Update contacts when peers join/leave and when project status changes

Antonio Scandurra created

Change summary

crates/collab/src/rpc.rs       | 555 +++++++++++++++++++----------------
crates/collab/src/rpc/store.rs | 101 +-----
2 files changed, 312 insertions(+), 344 deletions(-)

Detailed changes

crates/collab/src/rpc.rs 🔗

@@ -22,7 +22,7 @@ use axum::{
     routing::get,
     Extension, Router, TypedHeader,
 };
-use collections::{HashMap, HashSet};
+use collections::HashMap;
 use futures::{channel::mpsc, future::BoxFuture, FutureExt, SinkExt, StreamExt, TryStreamExt};
 use lazy_static::lazy_static;
 use rpc::{
@@ -49,7 +49,7 @@ use tokio::{
     time::Sleep,
 };
 use tower::ServiceBuilder;
-use tracing::{info_span, instrument, Instrument};
+use tracing::{info_span, Instrument};
 
 type MessageHandler =
     Box<dyn Send + Sync + Fn(Arc<Server>, Box<dyn AnyTypedEnvelope>) -> BoxFuture<'static, ()>>;
@@ -335,14 +335,10 @@ impl Server {
 
         for (project_id, project) in removed_connection.hosted_projects {
             if let Some(share) = project.share {
-                broadcast(
-                    connection_id,
-                    share.guests.keys().copied().collect(),
-                    |conn_id| {
-                        self.peer
-                            .send(conn_id, proto::UnshareProject { project_id })
-                    },
-                );
+                broadcast(connection_id, share.guests.keys().copied(), |conn_id| {
+                    self.peer
+                        .send(conn_id, proto::UnshareProject { project_id })
+                });
             }
         }
 
@@ -363,14 +359,12 @@ impl Server {
             .db
             .get_contacts(removed_connection.user_id)
             .await?;
+        let store = self.store().await;
         let mut update = proto::UpdateContacts::default();
-        update.contacts.push(proto::Contact {
-            user_id: removed_connection.user_id.to_proto(),
-            projects: Default::default(),
-            online: false,
-        });
+        update
+            .contacts
+            .push(store.contact_for_user(removed_connection.user_id));
 
-        let store = self.store().await;
         for user_id in contacts_to_update.current {
             for connection_id in store.connection_ids_for_user(user_id) {
                 self.peer.send(connection_id, update.clone()).trace_err();
@@ -407,10 +401,13 @@ impl Server {
         self: Arc<Server>,
         request: TypedEnvelope<proto::UnregisterProject>,
     ) -> Result<()> {
-        let mut state = self.store_mut().await;
-        let project = state.unregister_project(request.payload.project_id, request.sender_id)?;
-        // TODO
-        // self.update_contacts_for_users(&*state, &project.authorized_user_ids());
+        let user_id = {
+            let mut state = self.store_mut().await;
+            state.unregister_project(request.payload.project_id, request.sender_id)?;
+            state.user_id_for_connection(request.sender_id)?
+        };
+
+        self.update_user_contacts(user_id).await?;
         Ok(())
     }
 
@@ -419,27 +416,55 @@ impl Server {
         request: TypedEnvelope<proto::ShareProject>,
         response: Response<proto::ShareProject>,
     ) -> Result<()> {
-        let mut state = self.store_mut().await;
-        let project = state.share_project(request.payload.project_id, request.sender_id)?;
-        // TODO
-        // self.update_contacts_for_users(&mut *state, &project.authorized_user_ids);
+        let user_id = {
+            let mut state = self.store_mut().await;
+            state.share_project(request.payload.project_id, request.sender_id)?;
+            state.user_id_for_connection(request.sender_id)?
+        };
+        self.update_user_contacts(user_id).await?;
         response.send(proto::Ack {})?;
         Ok(())
     }
 
+    async fn update_user_contacts(self: &Arc<Server>, user_id: UserId) -> Result<()> {
+        let contacts = self.app_state.db.get_contacts(user_id).await?;
+        let store = self.store().await;
+        let updated_contact = store.contact_for_user(user_id);
+        for contact_user_id in contacts.current {
+            for contact_conn_id in store.connection_ids_for_user(contact_user_id) {
+                self.peer
+                    .send(
+                        contact_conn_id,
+                        proto::UpdateContacts {
+                            contacts: vec![updated_contact.clone()],
+                            remove_contacts: Default::default(),
+                            incoming_requests: Default::default(),
+                            remove_incoming_requests: Default::default(),
+                            outgoing_requests: Default::default(),
+                            remove_outgoing_requests: Default::default(),
+                        },
+                    )
+                    .trace_err();
+            }
+        }
+        Ok(())
+    }
+
     async fn unshare_project(
         self: Arc<Server>,
         request: TypedEnvelope<proto::UnshareProject>,
     ) -> Result<()> {
         let project_id = request.payload.project_id;
-        let mut state = self.store_mut().await;
-        let project = state.unshare_project(project_id, request.sender_id)?;
-        broadcast(request.sender_id, project.connection_ids, |conn_id| {
-            self.peer
-                .send(conn_id, proto::UnshareProject { project_id })
-        });
-        // TODO
-        // self.update_contacts_for_users(&mut *state, &project.authorized_user_ids);
+        let project;
+        {
+            let mut state = self.store_mut().await;
+            project = state.unshare_project(project_id, request.sender_id)?;
+            broadcast(request.sender_id, project.connection_ids, |conn_id| {
+                self.peer
+                    .send(conn_id, proto::UnshareProject { project_id })
+            });
+        }
+        self.update_user_contacts(project.host_user_id).await?;
         Ok(())
     }
 
@@ -449,74 +474,74 @@ impl Server {
         response: Response<proto::JoinProject>,
     ) -> Result<()> {
         let project_id = request.payload.project_id;
-
-        let state = &mut *self.store_mut().await;
-        let user_id = state.user_id_for_connection(request.sender_id)?;
-        let (response_payload, connection_ids, contact_user_ids) = state
-            .join_project(request.sender_id, user_id, project_id)
-            .and_then(|joined| {
-                let share = joined.project.share()?;
-                let peer_count = share.guests.len();
-                let mut collaborators = Vec::with_capacity(peer_count);
-                collaborators.push(proto::Collaborator {
-                    peer_id: joined.project.host_connection_id.0,
-                    replica_id: 0,
-                    user_id: joined.project.host_user_id.to_proto(),
-                });
-                let worktrees = share
-                    .worktrees
-                    .iter()
-                    .filter_map(|(id, shared_worktree)| {
-                        let worktree = joined.project.worktrees.get(&id)?;
-                        Some(proto::Worktree {
-                            id: *id,
-                            root_name: worktree.root_name.clone(),
-                            entries: shared_worktree.entries.values().cloned().collect(),
-                            diagnostic_summaries: shared_worktree
-                                .diagnostic_summaries
-                                .values()
-                                .cloned()
-                                .collect(),
-                            visible: worktree.visible,
-                            scan_id: shared_worktree.scan_id,
-                        })
+        let response_payload;
+        let host_user_id;
+        {
+            let state = &mut *self.store_mut().await;
+            let user_id = state.user_id_for_connection(request.sender_id)?;
+            let joined = state.join_project(request.sender_id, user_id, project_id)?;
+            let share = joined.project.share()?;
+            let peer_count = share.guests.len();
+            let mut collaborators = Vec::with_capacity(peer_count);
+            collaborators.push(proto::Collaborator {
+                peer_id: joined.project.host_connection_id.0,
+                replica_id: 0,
+                user_id: joined.project.host_user_id.to_proto(),
+            });
+            let worktrees = share
+                .worktrees
+                .iter()
+                .filter_map(|(id, shared_worktree)| {
+                    let worktree = joined.project.worktrees.get(&id)?;
+                    Some(proto::Worktree {
+                        id: *id,
+                        root_name: worktree.root_name.clone(),
+                        entries: shared_worktree.entries.values().cloned().collect(),
+                        diagnostic_summaries: shared_worktree
+                            .diagnostic_summaries
+                            .values()
+                            .cloned()
+                            .collect(),
+                        visible: worktree.visible,
+                        scan_id: shared_worktree.scan_id,
                     })
-                    .collect();
-                for (peer_conn_id, (peer_replica_id, peer_user_id)) in &share.guests {
-                    if *peer_conn_id != request.sender_id {
-                        collaborators.push(proto::Collaborator {
-                            peer_id: peer_conn_id.0,
-                            replica_id: *peer_replica_id as u32,
-                            user_id: peer_user_id.to_proto(),
-                        });
-                    }
+                })
+                .collect();
+            for (peer_conn_id, (peer_replica_id, peer_user_id)) in &share.guests {
+                if *peer_conn_id != request.sender_id {
+                    collaborators.push(proto::Collaborator {
+                        peer_id: peer_conn_id.0,
+                        replica_id: *peer_replica_id as u32,
+                        user_id: peer_user_id.to_proto(),
+                    });
                 }
-                let response = proto::JoinProjectResponse {
-                    worktrees,
-                    replica_id: joined.replica_id as u32,
-                    collaborators,
-                    language_servers: joined.project.language_servers.clone(),
-                };
-                let connection_ids = joined.project.connection_ids();
-                let contact_user_ids = joined.project.authorized_user_ids();
-                Ok((response, connection_ids, contact_user_ids))
-            })?;
-
-        broadcast(request.sender_id, connection_ids, |conn_id| {
-            self.peer.send(
-                conn_id,
-                proto::AddProjectCollaborator {
-                    project_id,
-                    collaborator: Some(proto::Collaborator {
-                        peer_id: request.sender_id.0,
-                        replica_id: response_payload.replica_id,
-                        user_id: user_id.to_proto(),
-                    }),
+            }
+            response_payload = proto::JoinProjectResponse {
+                worktrees,
+                replica_id: joined.replica_id as u32,
+                collaborators,
+                language_servers: joined.project.language_servers.clone(),
+            };
+            host_user_id = joined.project.host_user_id;
+            broadcast(
+                request.sender_id,
+                joined.project.connection_ids(),
+                |conn_id| {
+                    self.peer.send(
+                        conn_id,
+                        proto::AddProjectCollaborator {
+                            project_id,
+                            collaborator: Some(proto::Collaborator {
+                                peer_id: request.sender_id.0,
+                                replica_id: response_payload.replica_id,
+                                user_id: user_id.to_proto(),
+                            }),
+                        },
+                    )
                 },
-            )
-        });
-        // TODO
-        // self.update_contacts_for_users(state, &contact_user_ids);
+            );
+        }
+        self.update_user_contacts(host_user_id).await?;
         response.send(response_payload)?;
         Ok(())
     }
@@ -527,19 +552,21 @@ impl Server {
     ) -> Result<()> {
         let sender_id = request.sender_id;
         let project_id = request.payload.project_id;
-        let mut state = self.store_mut().await;
-        let worktree = state.leave_project(sender_id, project_id)?;
-        broadcast(sender_id, worktree.connection_ids, |conn_id| {
-            self.peer.send(
-                conn_id,
-                proto::RemoveProjectCollaborator {
-                    project_id,
-                    peer_id: sender_id.0,
-                },
-            )
-        });
-        // TODO
-        // self.update_contacts_for_users(&*state, &worktree.authorized_user_ids);
+        let project;
+        {
+            let mut state = self.store_mut().await;
+            project = state.leave_project(sender_id, project_id)?;
+            broadcast(sender_id, project.connection_ids, |conn_id| {
+                self.peer.send(
+                    conn_id,
+                    proto::RemoveProjectCollaborator {
+                        project_id,
+                        peer_id: sender_id.0,
+                    },
+                )
+            });
+        }
+        self.update_user_contacts(project.host_user_id).await?;
         Ok(())
     }
 
@@ -548,37 +575,30 @@ impl Server {
         request: TypedEnvelope<proto::RegisterWorktree>,
         response: Response<proto::RegisterWorktree>,
     ) -> Result<()> {
-        let mut contact_user_ids = HashSet::default();
-        for github_login in &request.payload.authorized_logins {
-            let contact_user_id = self.app_state.db.create_user(github_login, false).await?;
-            contact_user_ids.insert(contact_user_id);
-        }
-
-        let mut state = self.store_mut().await;
-        let host_user_id = state.user_id_for_connection(request.sender_id)?;
-        contact_user_ids.insert(host_user_id);
-
-        let contact_user_ids = contact_user_ids.into_iter().collect::<Vec<_>>();
-        let guest_connection_ids = state
-            .read_project(request.payload.project_id, request.sender_id)?
-            .guest_connection_ids();
-        state.register_worktree(
-            request.payload.project_id,
-            request.payload.worktree_id,
-            request.sender_id,
-            Worktree {
-                authorized_user_ids: contact_user_ids.clone(),
-                root_name: request.payload.root_name.clone(),
-                visible: request.payload.visible,
-            },
-        )?;
+        let host_user_id;
+        {
+            let mut state = self.store_mut().await;
+            host_user_id = state.user_id_for_connection(request.sender_id)?;
+
+            let guest_connection_ids = state
+                .read_project(request.payload.project_id, request.sender_id)?
+                .guest_connection_ids();
+            state.register_worktree(
+                request.payload.project_id,
+                request.payload.worktree_id,
+                request.sender_id,
+                Worktree {
+                    root_name: request.payload.root_name.clone(),
+                    visible: request.payload.visible,
+                },
+            )?;
 
-        broadcast(request.sender_id, guest_connection_ids, |connection_id| {
-            self.peer
-                .forward_send(request.sender_id, connection_id, request.payload.clone())
-        });
-        // TODO
-        // self.update_contacts_for_users(&*state, &contact_user_ids);
+            broadcast(request.sender_id, guest_connection_ids, |connection_id| {
+                self.peer
+                    .forward_send(request.sender_id, connection_id, request.payload.clone())
+            });
+        }
+        self.update_user_contacts(host_user_id).await?;
         response.send(proto::Ack {})?;
         Ok(())
     }
@@ -587,22 +607,25 @@ impl Server {
         self: Arc<Server>,
         request: TypedEnvelope<proto::UnregisterWorktree>,
     ) -> Result<()> {
+        let host_user_id;
         let project_id = request.payload.project_id;
         let worktree_id = request.payload.worktree_id;
-        let mut state = self.store_mut().await;
-        let (worktree, guest_connection_ids) =
-            state.unregister_worktree(project_id, worktree_id, request.sender_id)?;
-        broadcast(request.sender_id, guest_connection_ids, |conn_id| {
-            self.peer.send(
-                conn_id,
-                proto::UnregisterWorktree {
-                    project_id,
-                    worktree_id,
-                },
-            )
-        });
-        // TODO
-        // self.update_contacts_for_users(&*state, &worktree.authorized_user_ids);
+        {
+            let mut state = self.store_mut().await;
+            let (_, guest_connection_ids) =
+                state.unregister_worktree(project_id, worktree_id, request.sender_id)?;
+            host_user_id = state.user_id_for_connection(request.sender_id)?;
+            broadcast(request.sender_id, guest_connection_ids, |conn_id| {
+                self.peer.send(
+                    conn_id,
+                    proto::UnregisterWorktree {
+                        project_id,
+                        worktree_id,
+                    },
+                )
+            });
+        }
+        self.update_user_contacts(host_user_id).await?;
         Ok(())
     }
 
@@ -950,8 +973,7 @@ impl Server {
         response: Response<proto::RequestContact>,
     ) -> Result<()> {
         let requester_id = self
-            .store
-            .read()
+            .store()
             .await
             .user_id_for_connection(request.sender_id)?;
         let responder_id = UserId::from_proto(request.payload.responder_id);
@@ -989,8 +1011,7 @@ impl Server {
         response: Response<proto::RespondToContactRequest>,
     ) -> Result<()> {
         let responder_id = self
-            .store
-            .read()
+            .store()
             .await
             .user_id_for_connection(request.sender_id)?;
         let requester_id = UserId::from_proto(request.payload.requester_id);
@@ -1000,45 +1021,28 @@ impl Server {
             .respond_to_contact_request(responder_id, requester_id, accept)
             .await?;
 
+        let store = self.store().await;
         // Update responder with new contact
         let mut update = proto::UpdateContacts::default();
         if accept {
-            update.contacts.push(proto::Contact {
-                user_id: requester_id.to_proto(),
-                projects: Default::default(), // TODO
-                online: true,                 // TODO
-            });
+            update.contacts.push(store.contact_for_user(requester_id));
         }
         update
             .remove_incoming_requests
             .push(requester_id.to_proto());
-        for connection_id in self
-            .store
-            .read()
-            .await
-            .connection_ids_for_user(responder_id)
-        {
+        for connection_id in store.connection_ids_for_user(responder_id) {
             self.peer.send(connection_id, update.clone())?;
         }
 
         // Update requester with new contact
         let mut update = proto::UpdateContacts::default();
         if accept {
-            update.contacts.push(proto::Contact {
-                user_id: responder_id.to_proto(),
-                projects: Default::default(), // TODO
-                online: true,                 // TODO
-            });
+            update.contacts.push(store.contact_for_user(responder_id));
         }
         update
             .remove_outgoing_requests
             .push(responder_id.to_proto());
-        for connection_id in self
-            .store
-            .read()
-            .await
-            .connection_ids_for_user(requester_id)
-        {
+        for connection_id in store.connection_ids_for_user(requester_id) {
             self.peer.send(connection_id, update.clone())?;
         }
 
@@ -1312,9 +1316,11 @@ impl Executor for RealExecutor {
     }
 }
 
-#[instrument(skip(f))]
-fn broadcast<F>(sender_id: ConnectionId, receiver_ids: Vec<ConnectionId>, mut f: F)
-where
+fn broadcast<F>(
+    sender_id: ConnectionId,
+    receiver_ids: impl IntoIterator<Item = ConnectionId>,
+    mut f: F,
+) where
     F: FnMut(ConnectionId) -> anyhow::Result<()>,
 {
     for receiver_id in receiver_ids {
@@ -1461,7 +1467,7 @@ mod tests {
         self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Credentials,
         EstablishConnectionError, UserStore, RECEIVE_TIMEOUT,
     };
-    use collections::BTreeMap;
+    use collections::{BTreeMap, HashSet};
     use editor::{
         self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Input, Redo, Rename,
         ToOffset, ToggleCodeActions, Undo,
@@ -4890,6 +4896,7 @@ mod tests {
 
     #[gpui::test(iterations = 10)]
     async fn test_contacts(
+        deterministic: Arc<Deterministic>,
         cx_a: &mut TestAppContext,
         cx_b: &mut TestAppContext,
         cx_c: &mut TestAppContext,
@@ -4903,15 +4910,26 @@ mod tests {
         let client_a = server.create_client(cx_a, "user_a").await;
         let client_b = server.create_client(cx_b, "user_b").await;
         let client_c = server.create_client(cx_c, "user_c").await;
+        server
+            .make_contacts(vec![
+                (&client_a, cx_a),
+                (&client_b, cx_b),
+                (&client_c, cx_c),
+            ])
+            .await;
+        deterministic.run_until_parked();
+        client_a.user_store.read_with(cx_a, |store, _| {
+            assert_eq!(contacts(store), [("user_b", vec![]), ("user_c", vec![])])
+        });
+        client_b.user_store.read_with(cx_b, |store, _| {
+            assert_eq!(contacts(store), [("user_a", vec![]), ("user_c", vec![])])
+        });
+        client_c.user_store.read_with(cx_c, |store, _| {
+            assert_eq!(contacts(store), [("user_a", vec![]), ("user_b", vec![])])
+        });
 
         // Share a worktree as client A.
-        fs.insert_tree(
-            "/a",
-            json!({
-                ".zed.toml": r#"collaborators = ["user_b", "user_c"]"#,
-            }),
-        )
-        .await;
+        fs.create_dir(Path::new("/a")).await.unwrap();
 
         let project_a = cx_a.update(|cx| {
             Project::local(
@@ -4932,24 +4950,22 @@ mod tests {
             .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
             .await;
 
-        client_a
-            .user_store
-            .condition(&cx_a, |user_store, _| {
-                contacts(user_store) == vec![("user_a", vec![("a", false, vec![])])]
-            })
-            .await;
-        client_b
-            .user_store
-            .condition(&cx_b, |user_store, _| {
-                contacts(user_store) == vec![("user_a", vec![("a", false, vec![])])]
-            })
-            .await;
-        client_c
-            .user_store
-            .condition(&cx_c, |user_store, _| {
-                contacts(user_store) == vec![("user_a", vec![("a", false, vec![])])]
-            })
-            .await;
+        deterministic.run_until_parked();
+        client_a.user_store.read_with(cx_a, |store, _| {
+            assert_eq!(contacts(store), [("user_b", vec![]), ("user_c", vec![])])
+        });
+        client_b.user_store.read_with(cx_b, |store, _| {
+            assert_eq!(
+                contacts(store),
+                [("user_a", vec![("a", false, vec![])]), ("user_c", vec![])]
+            )
+        });
+        client_c.user_store.read_with(cx_c, |store, _| {
+            assert_eq!(
+                contacts(store),
+                [("user_a", vec![("a", false, vec![])]), ("user_b", vec![])]
+            )
+        });
 
         let project_id = project_a
             .update(cx_a, |project, _| project.next_remote_id())
@@ -4958,24 +4974,22 @@ mod tests {
             .update(cx_a, |project, cx| project.share(cx))
             .await
             .unwrap();
-        client_a
-            .user_store
-            .condition(&cx_a, |user_store, _| {
-                contacts(user_store) == vec![("user_a", vec![("a", true, vec![])])]
-            })
-            .await;
-        client_b
-            .user_store
-            .condition(&cx_b, |user_store, _| {
-                contacts(user_store) == vec![("user_a", vec![("a", true, vec![])])]
-            })
-            .await;
-        client_c
-            .user_store
-            .condition(&cx_c, |user_store, _| {
-                contacts(user_store) == vec![("user_a", vec![("a", true, vec![])])]
-            })
-            .await;
+        deterministic.run_until_parked();
+        client_a.user_store.read_with(cx_a, |store, _| {
+            assert_eq!(contacts(store), [("user_b", vec![]), ("user_c", vec![])])
+        });
+        client_b.user_store.read_with(cx_b, |store, _| {
+            assert_eq!(
+                contacts(store),
+                [("user_a", vec![("a", true, vec![])]), ("user_c", vec![])]
+            )
+        });
+        client_c.user_store.read_with(cx_c, |store, _| {
+            assert_eq!(
+                contacts(store),
+                [("user_a", vec![("a", true, vec![])]), ("user_b", vec![])]
+            )
+        });
 
         let _project_b = Project::remote(
             project_id,
@@ -4987,25 +5001,28 @@ mod tests {
         )
         .await
         .unwrap();
-
-        client_a
-            .user_store
-            .condition(&cx_a, |user_store, _| {
-                contacts(user_store) == vec![("user_a", vec![("a", true, vec!["user_b"])])]
-            })
-            .await;
-        client_b
-            .user_store
-            .condition(&cx_b, |user_store, _| {
-                contacts(user_store) == vec![("user_a", vec![("a", true, vec!["user_b"])])]
-            })
-            .await;
-        client_c
-            .user_store
-            .condition(&cx_c, |user_store, _| {
-                contacts(user_store) == vec![("user_a", vec![("a", true, vec!["user_b"])])]
-            })
-            .await;
+        deterministic.run_until_parked();
+        client_a.user_store.read_with(cx_a, |store, _| {
+            assert_eq!(contacts(store), [("user_b", vec![]), ("user_c", vec![])])
+        });
+        client_b.user_store.read_with(cx_b, |store, _| {
+            assert_eq!(
+                contacts(store),
+                [
+                    ("user_a", vec![("a", true, vec!["user_b"])]),
+                    ("user_c", vec![])
+                ]
+            )
+        });
+        client_c.user_store.read_with(cx_c, |store, _| {
+            assert_eq!(
+                contacts(store),
+                [
+                    ("user_a", vec![("a", true, vec!["user_b"])]),
+                    ("user_b", vec![])
+                ]
+            )
+        });
 
         project_a
             .condition(&cx_a, |project, _| {
@@ -5014,18 +5031,16 @@ mod tests {
             .await;
 
         cx_a.update(move |_| drop(project_a));
-        client_a
-            .user_store
-            .condition(&cx_a, |user_store, _| contacts(user_store) == vec![])
-            .await;
-        client_b
-            .user_store
-            .condition(&cx_b, |user_store, _| contacts(user_store) == vec![])
-            .await;
-        client_c
-            .user_store
-            .condition(&cx_c, |user_store, _| contacts(user_store) == vec![])
-            .await;
+        deterministic.run_until_parked();
+        client_a.user_store.read_with(cx_a, |store, _| {
+            assert_eq!(contacts(store), [("user_b", vec![]), ("user_c", vec![])])
+        });
+        client_b.user_store.read_with(cx_b, |store, _| {
+            assert_eq!(contacts(store), [("user_a", vec![]), ("user_c", vec![])])
+        });
+        client_c.user_store.read_with(cx_c, |store, _| {
+            assert_eq!(contacts(store), [("user_a", vec![]), ("user_b", vec![])])
+        });
 
         fn contacts(user_store: &UserStore) -> Vec<(&str, Vec<(&str, bool, Vec<&str>)>)> {
             user_store
@@ -6370,6 +6385,28 @@ mod tests {
             self.forbid_connections.store(false, SeqCst);
         }
 
+        async fn make_contacts(&self, mut clients: Vec<(&TestClient, &mut TestAppContext)>) {
+            while let Some((client_a, cx_a)) = clients.pop() {
+                for (client_b, cx_b) in &mut clients {
+                    client_a
+                        .user_store
+                        .update(cx_a, |store, _| {
+                            store.request_contact(client_b.user_id().unwrap())
+                        })
+                        .await
+                        .unwrap();
+                    cx_a.foreground().run_until_parked();
+                    client_b
+                        .user_store
+                        .update(*cx_b, |store, _| {
+                            store.respond_to_contact_request(client_a.user_id().unwrap(), true)
+                        })
+                        .await
+                        .unwrap();
+                }
+            }
+        }
+
         async fn build_app_state(test_db: &TestDb) -> Arc<AppState> {
             Arc::new(AppState {
                 db: test_db.db().clone(),
@@ -6457,7 +6494,7 @@ mod tests {
         }
 
         fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
-            self.user_store.read_with(cx, |store, cx| ContactsSummary {
+            self.user_store.read_with(cx, |store, _| ContactsSummary {
                 current: store
                     .contacts()
                     .iter()

crates/collab/src/rpc/store.rs 🔗

@@ -10,7 +10,6 @@ pub struct Store {
     connections: HashMap<ConnectionId, ConnectionState>,
     connections_by_user_id: HashMap<UserId, HashSet<ConnectionId>>,
     projects: HashMap<u64, Project>,
-    visible_projects_by_user_id: HashMap<UserId, HashSet<u64>>,
     channels: HashMap<ChannelId, Channel>,
     next_project_id: u64,
 }
@@ -30,7 +29,6 @@ pub struct Project {
 }
 
 pub struct Worktree {
-    pub authorized_user_ids: Vec<UserId>,
     pub root_name: String,
     pub visible: bool,
 }
@@ -69,18 +67,16 @@ pub struct JoinedProject<'a> {
     pub project: &'a Project,
 }
 
-pub struct SharedProject {
-    pub authorized_user_ids: Vec<UserId>,
-}
+pub struct SharedProject {}
 
 pub struct UnsharedProject {
     pub connection_ids: Vec<ConnectionId>,
-    pub authorized_user_ids: Vec<UserId>,
+    pub host_user_id: UserId,
 }
 
 pub struct LeftProject {
     pub connection_ids: Vec<ConnectionId>,
-    pub authorized_user_ids: Vec<UserId>,
+    pub host_user_id: UserId,
 }
 
 #[derive(Copy, Clone)]
@@ -155,13 +151,11 @@ impl Store {
         result.user_id = connection.user_id;
         for project_id in connection.projects.clone() {
             if let Ok(project) = self.unregister_project(project_id, connection_id) {
-                result.contact_ids.extend(project.authorized_user_ids());
                 result.hosted_projects.insert(project_id, project);
             } else if let Ok(project) = self.leave_project(connection_id, project_id) {
                 result
                     .guest_project_ids
                     .insert(project_id, project.connection_ids);
-                result.contact_ids.extend(project.authorized_user_ids);
             }
         }
 
@@ -215,6 +209,14 @@ impl Store {
             .copied()
     }
 
+    pub fn is_user_online(&self, user_id: UserId) -> bool {
+        !self
+            .connections_by_user_id
+            .get(&user_id)
+            .unwrap_or(&Default::default())
+            .is_empty()
+    }
+
     pub fn build_initial_contacts_update(&self, contacts: db::Contacts) -> proto::UpdateContacts {
         let mut update = proto::UpdateContacts::default();
         for user_id in contacts.current {
@@ -241,7 +243,7 @@ impl Store {
         proto::Contact {
             user_id: user_id.to_proto(),
             projects: self.project_metadata_for_user(user_id),
-            online: self.connection_ids_for_user(user_id).next().is_some(),
+            online: self.is_user_online(user_id),
         }
     }
 
@@ -359,13 +361,6 @@ impl Store {
             .get_mut(&project_id)
             .ok_or_else(|| anyhow!("no such project"))?;
         if project.host_connection_id == connection_id {
-            for authorized_user_id in &worktree.authorized_user_ids {
-                self.visible_projects_by_user_id
-                    .entry(*authorized_user_id)
-                    .or_default()
-                    .insert(project_id);
-            }
-
             project.worktrees.insert(worktree_id, worktree);
             if let Ok(share) = project.share_mut() {
                 share.worktrees.insert(worktree_id, Default::default());
@@ -385,14 +380,6 @@ impl Store {
         match self.projects.entry(project_id) {
             hash_map::Entry::Occupied(e) => {
                 if e.get().host_connection_id == connection_id {
-                    for user_id in e.get().authorized_user_ids() {
-                        if let hash_map::Entry::Occupied(mut projects) =
-                            self.visible_projects_by_user_id.entry(user_id)
-                        {
-                            projects.get_mut().remove(&project_id);
-                        }
-                    }
-
                     let project = e.remove();
 
                     if let Some(host_connection) = self.connections.get_mut(&connection_id) {
@@ -441,16 +428,6 @@ impl Store {
             share.worktrees.remove(&worktree_id);
         }
 
-        for authorized_user_id in &worktree.authorized_user_ids {
-            if let Some(visible_projects) =
-                self.visible_projects_by_user_id.get_mut(authorized_user_id)
-            {
-                if !project.has_authorized_user_id(*authorized_user_id) {
-                    visible_projects.remove(&project_id);
-                }
-            }
-        }
-
         Ok((worktree, guest_connection_ids))
     }
 
@@ -466,9 +443,7 @@ impl Store {
                     share.worktrees.insert(*worktree_id, Default::default());
                 }
                 project.share = Some(share);
-                return Ok(SharedProject {
-                    authorized_user_ids: project.authorized_user_ids(),
-                });
+                return Ok(SharedProject {});
             }
         }
         Err(anyhow!("no such project"))?
@@ -490,7 +465,6 @@ impl Store {
         }
 
         let connection_ids = project.connection_ids();
-        let authorized_user_ids = project.authorized_user_ids();
         if let Some(share) = project.share.take() {
             for connection_id in share.guests.into_keys() {
                 if let Some(connection) = self.connections.get_mut(&connection_id) {
@@ -500,7 +474,7 @@ impl Store {
 
             Ok(UnsharedProject {
                 connection_ids,
-                authorized_user_ids,
+                host_user_id: project.host_user_id,
             })
         } else {
             Err(anyhow!("project is not shared"))?
@@ -564,13 +538,6 @@ impl Store {
         let project = self
             .projects
             .get_mut(&project_id)
-            .and_then(|project| {
-                if project.has_authorized_user_id(user_id) {
-                    Some(project)
-                } else {
-                    None
-                }
-            })
             .ok_or_else(|| anyhow!("no such project"))?;
 
         let share = project.share_mut()?;
@@ -612,12 +579,9 @@ impl Store {
             connection.projects.remove(&project_id);
         }
 
-        let connection_ids = project.connection_ids();
-        let authorized_user_ids = project.authorized_user_ids();
-
         Ok(LeftProject {
-            connection_ids,
-            authorized_user_ids,
+            connection_ids: project.connection_ids(),
+            host_user_id: project.host_user_id,
         })
     }
 
@@ -767,14 +731,6 @@ impl Store {
             let host_connection = self.connections.get(&project.host_connection_id).unwrap();
             assert!(host_connection.projects.contains(project_id));
 
-            for authorized_user_ids in project.authorized_user_ids() {
-                let visible_project_ids = self
-                    .visible_projects_by_user_id
-                    .get(&authorized_user_ids)
-                    .unwrap();
-                assert!(visible_project_ids.contains(project_id));
-            }
-
             if let Some(share) = &project.share {
                 for guest_connection_id in share.guests.keys() {
                     let guest_connection = self.connections.get(guest_connection_id).unwrap();
@@ -792,13 +748,6 @@ impl Store {
             }
         }
 
-        for (user_id, visible_project_ids) in &self.visible_projects_by_user_id {
-            for project_id in visible_project_ids {
-                let project = self.projects.get(project_id).unwrap();
-                assert!(project.authorized_user_ids().contains(user_id));
-            }
-        }
-
         for (channel_id, channel) in &self.channels {
             for connection_id in &channel.connection_ids {
                 let connection = self.connections.get(connection_id).unwrap();
@@ -809,24 +758,6 @@ impl Store {
 }
 
 impl Project {
-    pub fn has_authorized_user_id(&self, user_id: UserId) -> bool {
-        self.worktrees
-            .values()
-            .any(|worktree| worktree.authorized_user_ids.contains(&user_id))
-    }
-
-    pub fn authorized_user_ids(&self) -> Vec<UserId> {
-        let mut ids = self
-            .worktrees
-            .values()
-            .flat_map(|worktree| worktree.authorized_user_ids.iter())
-            .copied()
-            .collect::<Vec<_>>();
-        ids.sort_unstable();
-        ids.dedup();
-        ids
-    }
-
     pub fn guest_connection_ids(&self) -> Vec<ConnectionId> {
         if let Some(share) = &self.share {
             share.guests.keys().copied().collect()