Remove project join requests

Antonio Scandurra created

Change summary

crates/collab/src/integration_tests.rs                 | 196 -----
crates/collab/src/rpc.rs                               | 411 +++--------
crates/collab/src/rpc/store.rs                         | 224 +----
crates/contacts_panel/src/contacts_panel.rs            |  67 -
crates/contacts_panel/src/join_project_notification.rs |  80 --
crates/project/src/project.rs                          | 341 +--------
crates/rpc/proto/zed.proto                             |  53 -
crates/rpc/src/proto.rs                                |   9 
crates/workspace/src/workspace.rs                      |  32 
crates/zed/src/main.rs                                 |   2 
crates/zed/src/zed.rs                                  |   7 
11 files changed, 275 insertions(+), 1,147 deletions(-)

Detailed changes

crates/collab/src/integration_tests.rs 🔗

@@ -7,7 +7,7 @@ use ::rpc::Peer;
 use anyhow::anyhow;
 use call::Room;
 use client::{
-    self, proto, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
+    self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
     Credentials, EstablishConnectionError, UserStore, RECEIVE_TIMEOUT,
 };
 use collections::{BTreeMap, HashMap, HashSet};
@@ -40,7 +40,6 @@ use serde_json::json;
 use settings::{Formatter, Settings};
 use sqlx::types::time::OffsetDateTime;
 use std::{
-    cell::RefCell,
     env,
     ops::Deref,
     path::{Path, PathBuf},
@@ -459,12 +458,15 @@ async fn test_unshare_project(
         .await
         .unwrap();
 
-    // When client B leaves the project, it gets automatically unshared.
-    cx_b.update(|_| drop(project_b));
+    // When client A unshares the project, client B's project becomes read-only.
+    project_a
+        .update(cx_a, |project, cx| project.unshare(cx))
+        .unwrap();
     deterministic.run_until_parked();
     assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
+    assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
 
-    // When client B joins again, the project gets re-shared.
+    // Client B can join again after client A re-shares.
     let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
     project_b2
@@ -515,7 +517,7 @@ async fn test_host_disconnect(
 
     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
     let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
-    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
+    project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 
     let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
@@ -539,20 +541,6 @@ async fn test_host_disconnect(
     editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
     assert!(cx_b.is_window_edited(workspace_b.window_id()));
 
-    // Request to join that project as client C
-    let project_c = cx_c.spawn(|cx| {
-        Project::remote(
-            project_id,
-            client_c.client.clone(),
-            client_c.user_store.clone(),
-            client_c.project_store.clone(),
-            client_c.language_registry.clone(),
-            FakeFs::new(cx.background()),
-            cx,
-        )
-    });
-    deterministic.run_until_parked();
-
     // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
     server.disconnect_client(client_a.current_user_id(cx_a));
     cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
@@ -564,10 +552,6 @@ async fn test_host_disconnect(
         .condition(cx_b, |project, _| project.is_read_only())
         .await;
     assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
-    assert!(matches!(
-        project_c.await.unwrap_err(),
-        project::JoinProjectError::HostWentOffline
-    ));
 
     // Ensure client B's edited state is reset and that the whole window is blurred.
     cx_b.read(|cx| {
@@ -598,139 +582,6 @@ async fn test_host_disconnect(
         .unwrap();
 }
 
-#[gpui::test(iterations = 10)]
-async fn test_decline_join_request(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    server
-        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-
-    client_a.fs.insert_tree("/a", json!({})).await;
-
-    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
-
-    // Request to join that project as client B
-    let project_b = cx_b.spawn(|cx| {
-        Project::remote(
-            project_id,
-            client_b.client.clone(),
-            client_b.user_store.clone(),
-            client_b.project_store.clone(),
-            client_b.language_registry.clone(),
-            FakeFs::new(cx.background()),
-            cx,
-        )
-    });
-    deterministic.run_until_parked();
-    project_a.update(cx_a, |project, cx| {
-        project.respond_to_join_request(client_b.user_id().unwrap(), false, cx)
-    });
-    assert!(matches!(
-        project_b.await.unwrap_err(),
-        project::JoinProjectError::HostDeclined
-    ));
-
-    // Request to join the project again as client B
-    let project_b = cx_b.spawn(|cx| {
-        Project::remote(
-            project_id,
-            client_b.client.clone(),
-            client_b.user_store.clone(),
-            client_b.project_store.clone(),
-            client_b.language_registry.clone(),
-            FakeFs::new(cx.background()),
-            cx,
-        )
-    });
-
-    // Close the project on the host
-    deterministic.run_until_parked();
-    cx_a.update(|_| drop(project_a));
-    deterministic.run_until_parked();
-    assert!(matches!(
-        project_b.await.unwrap_err(),
-        project::JoinProjectError::HostClosedProject
-    ));
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_cancel_join_request(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-) {
-    cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    server
-        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
-        .await;
-
-    client_a.fs.insert_tree("/a", json!({})).await;
-    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
-
-    let user_b = client_a
-        .user_store
-        .update(cx_a, |store, cx| {
-            store.get_user(client_b.user_id().unwrap(), cx)
-        })
-        .await
-        .unwrap();
-
-    let project_a_events = Rc::new(RefCell::new(Vec::new()));
-    project_a.update(cx_a, {
-        let project_a_events = project_a_events.clone();
-        move |_, cx| {
-            cx.subscribe(&cx.handle(), move |_, _, event, _| {
-                project_a_events.borrow_mut().push(event.clone());
-            })
-            .detach();
-        }
-    });
-
-    // Request to join that project as client B
-    let project_b = cx_b.spawn(|cx| {
-        Project::remote(
-            project_id,
-            client_b.client.clone(),
-            client_b.user_store.clone(),
-            client_b.project_store.clone(),
-            client_b.language_registry.clone(),
-            FakeFs::new(cx.background()),
-            cx,
-        )
-    });
-    deterministic.run_until_parked();
-    assert_eq!(
-        &*project_a_events.borrow(),
-        &[project::Event::ContactRequestedJoin(user_b.clone())]
-    );
-    project_a_events.borrow_mut().clear();
-
-    // Cancel the join request by leaving the project
-    client_b
-        .client
-        .send(proto::LeaveProject { project_id })
-        .unwrap();
-    drop(project_b);
-
-    deterministic.run_until_parked();
-    assert_eq!(
-        &*project_a_events.borrow(),
-        &[project::Event::ContactCancelledJoinRequest(user_b)]
-    );
-}
-
 #[gpui::test(iterations = 10)]
 async fn test_propagate_saves_and_fs_changes(
     cx_a: &mut TestAppContext,
@@ -4586,7 +4437,6 @@ async fn test_random_collaboration(
     let host = server.create_client(&mut host_cx, "host").await;
     let host_project = host_cx.update(|cx| {
         Project::local(
-            true,
             host.client.clone(),
             host.user_store.clone(),
             host.project_store.clone(),
@@ -4738,6 +4588,11 @@ async fn test_random_collaboration(
         .await;
     host_language_registry.add(Arc::new(language));
 
+    host_project
+        .update(&mut host_cx, |project, cx| project.share(cx))
+        .await
+        .unwrap();
+
     let op_start_signal = futures::channel::mpsc::unbounded();
     user_ids.push(host.current_user_id(&host_cx));
     op_start_signals.push(op_start_signal.0);
@@ -5097,7 +4952,7 @@ impl TestServer {
 
         let fs = FakeFs::new(cx.background());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
-        let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
+        let project_store = cx.add_model(|_| ProjectStore::new());
         let app_state = Arc::new(workspace::AppState {
             client: client.clone(),
             user_store: user_store.clone(),
@@ -5283,7 +5138,6 @@ impl TestClient {
     ) -> (ModelHandle<Project>, WorktreeId) {
         let project = cx.update(|cx| {
             Project::local(
-                true,
                 self.client.clone(),
                 self.user_store.clone(),
                 self.project_store.clone(),
@@ -5316,7 +5170,10 @@ impl TestClient {
         let host_project_id = host_project
             .read_with(host_cx, |project, _| project.next_remote_id())
             .await;
-        let guest_user_id = self.user_id().unwrap();
+        host_project
+            .update(host_cx, |project, cx| project.share(cx))
+            .await
+            .unwrap();
         let languages = host_project.read_with(host_cx, |project, _| project.languages().clone());
         let project_b = guest_cx.spawn(|cx| {
             Project::remote(
@@ -5329,10 +5186,7 @@ impl TestClient {
                 cx,
             )
         });
-        host_cx.foreground().run_until_parked();
-        host_project.update(host_cx, |project, cx| {
-            project.respond_to_join_request(guest_user_id, true, cx)
-        });
+
         let project = project_b.await.unwrap();
         project
     }
@@ -5369,18 +5223,6 @@ impl TestClient {
         ) -> anyhow::Result<()> {
             let fs = project.read_with(cx, |project, _| project.fs().clone());
 
-            cx.update(|cx| {
-                cx.subscribe(&project, move |project, event, cx| {
-                    if let project::Event::ContactRequestedJoin(user) = event {
-                        log::info!("Host: accepting join request from {}", user.github_login);
-                        project.update(cx, |project, cx| {
-                            project.respond_to_join_request(user.id, true, cx)
-                        });
-                    }
-                })
-                .detach();
-            });
-
             while op_start_signal.next().await.is_some() {
                 let distribution = rng.lock().gen_range::<usize, _>(0..100);
                 let files = fs.as_fake().files().await;

crates/collab/src/rpc.rs 🔗

@@ -88,11 +88,6 @@ impl<R: RequestMessage> Response<R> {
         self.server.peer.respond(self.receipt, payload)?;
         Ok(())
     }
-
-    fn into_receipt(self) -> Receipt<R> {
-        self.responded.store(true, SeqCst);
-        self.receipt
-    }
 }
 
 pub struct Server {
@@ -160,7 +155,7 @@ impl Server {
             .add_request_handler(Server::unregister_project)
             .add_request_handler(Server::join_project)
             .add_message_handler(Server::leave_project)
-            .add_message_handler(Server::respond_to_join_project_request)
+            .add_message_handler(Server::unshare_project)
             .add_message_handler(Server::update_project)
             .add_message_handler(Server::register_project_activity)
             .add_request_handler(Server::update_worktree)
@@ -491,21 +486,6 @@ impl Server {
                         },
                     )
                 });
-
-                for (_, receipts) in project.join_requests {
-                    for receipt in receipts {
-                        self.peer.respond(
-                            receipt,
-                            proto::JoinProjectResponse {
-                                variant: Some(proto::join_project_response::Variant::Decline(
-                                    proto::join_project_response::Decline {
-                                        reason: proto::join_project_response::decline::Reason::WentOffline as i32
-                                    },
-                                )),
-                            },
-                        )?;
-                    }
-                }
             }
 
             for project_id in removed_connection.guest_project_ids {
@@ -519,16 +499,6 @@ impl Server {
                             },
                         )
                     });
-                    if project.guests.is_empty() {
-                        self.peer
-                            .send(
-                                project.host_connection_id,
-                                proto::ProjectUnshared {
-                                    project_id: project_id.to_proto(),
-                                },
-                            )
-                            .trace_err();
-                    }
                 }
             }
 
@@ -727,11 +697,9 @@ impl Server {
             .await
             .user_id_for_connection(request.sender_id)?;
         let project_id = self.app_state.db.register_project(user_id).await?;
-        self.store().await.register_project(
-            request.sender_id,
-            project_id,
-            request.payload.online,
-        )?;
+        self.store()
+            .await
+            .register_project(request.sender_id, project_id)?;
 
         response.send(proto::RegisterProjectResponse {
             project_id: project_id.to_proto(),
@@ -746,11 +714,10 @@ impl Server {
         response: Response<proto::UnregisterProject>,
     ) -> Result<()> {
         let project_id = ProjectId::from_proto(request.payload.project_id);
-        let (user_id, project) = {
-            let mut state = self.store().await;
-            let project = state.unregister_project(project_id, request.sender_id)?;
-            (state.user_id_for_connection(request.sender_id)?, project)
-        };
+        let project = self
+            .store()
+            .await
+            .unregister_project(project_id, request.sender_id)?;
         self.app_state.db.unregister_project(project_id).await?;
 
         broadcast(
@@ -765,32 +732,27 @@ impl Server {
                 )
             },
         );
-        for (_, receipts) in project.join_requests {
-            for receipt in receipts {
-                self.peer.respond(
-                    receipt,
-                    proto::JoinProjectResponse {
-                        variant: Some(proto::join_project_response::Variant::Decline(
-                            proto::join_project_response::Decline {
-                                reason: proto::join_project_response::decline::Reason::Closed
-                                    as i32,
-                            },
-                        )),
-                    },
-                )?;
-            }
-        }
-
-        // Send out the `UpdateContacts` message before responding to the unregister
-        // request. This way, when the project's host can keep track of the project's
-        // remote id until after they've received the `UpdateContacts` message for
-        // themself.
-        self.update_user_contacts(user_id).await?;
         response.send(proto::Ack {})?;
 
         Ok(())
     }
 
+    async fn unshare_project(
+        self: Arc<Server>,
+        message: TypedEnvelope<proto::UnshareProject>,
+    ) -> Result<()> {
+        let project_id = ProjectId::from_proto(message.payload.project_id);
+        let project = self
+            .store()
+            .await
+            .unshare_project(project_id, message.sender_id)?;
+        broadcast(message.sender_id, project.guest_connection_ids, |conn_id| {
+            self.peer.send(conn_id, message.payload.clone())
+        });
+
+        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;
@@ -849,167 +811,93 @@ impl Server {
             return Err(anyhow!("no such project"))?;
         }
 
-        self.store().await.request_join_project(
-            guest_user_id,
-            project_id,
-            response.into_receipt(),
-        )?;
-        self.peer.send(
-            host_connection_id,
-            proto::RequestJoinProject {
-                project_id: project_id.to_proto(),
-                requester_id: guest_user_id.to_proto(),
-            },
-        )?;
-        Ok(())
-    }
-
-    async fn respond_to_join_project_request(
-        self: Arc<Server>,
-        request: TypedEnvelope<proto::RespondToJoinProjectRequest>,
-    ) -> Result<()> {
-        let host_user_id;
+        let mut store = self.store().await;
+        let (project, replica_id) = store.join_project(request.sender_id, project_id)?;
+        let peer_count = project.guests.len();
+        let mut collaborators = Vec::with_capacity(peer_count);
+        collaborators.push(proto::Collaborator {
+            peer_id: project.host_connection_id.0,
+            replica_id: 0,
+            user_id: project.host.user_id.to_proto(),
+        });
+        let worktrees = project
+            .worktrees
+            .iter()
+            .map(|(id, worktree)| proto::WorktreeMetadata {
+                id: *id,
+                root_name: worktree.root_name.clone(),
+                visible: worktree.visible,
+            })
+            .collect::<Vec<_>>();
 
-        {
-            let mut state = self.store().await;
-            let project_id = ProjectId::from_proto(request.payload.project_id);
-            let project = state.project(project_id)?;
-            if project.host_connection_id != request.sender_id {
-                Err(anyhow!("no such connection"))?;
+        // Add all guests other than the requesting user's own connections as collaborators
+        for (guest_conn_id, guest) in &project.guests {
+            if request.sender_id != *guest_conn_id {
+                collaborators.push(proto::Collaborator {
+                    peer_id: guest_conn_id.0,
+                    replica_id: guest.replica_id as u32,
+                    user_id: guest.user_id.to_proto(),
+                });
             }
+        }
 
-            host_user_id = project.host.user_id;
-            let guest_user_id = UserId::from_proto(request.payload.requester_id);
-
-            if !request.payload.allow {
-                let receipts = state
-                    .deny_join_project_request(request.sender_id, guest_user_id, project_id)
-                    .ok_or_else(|| anyhow!("no such request"))?;
-                for receipt in receipts {
-                    self.peer.respond(
-                        receipt,
-                        proto::JoinProjectResponse {
-                            variant: Some(proto::join_project_response::Variant::Decline(
-                                proto::join_project_response::Decline {
-                                    reason: proto::join_project_response::decline::Reason::Declined
-                                        as i32,
-                                },
-                            )),
-                        },
-                    )?;
-                }
-                return Ok(());
+        for conn_id in project.connection_ids() {
+            if conn_id != request.sender_id {
+                self.peer.send(
+                    conn_id,
+                    proto::AddProjectCollaborator {
+                        project_id: project_id.to_proto(),
+                        collaborator: Some(proto::Collaborator {
+                            peer_id: request.sender_id.0,
+                            replica_id: replica_id as u32,
+                            user_id: guest_user_id.to_proto(),
+                        }),
+                    },
+                )?;
             }
+        }
 
-            let (receipts_with_replica_ids, project) = state
-                .accept_join_project_request(request.sender_id, guest_user_id, project_id)
-                .ok_or_else(|| anyhow!("no such request"))?;
+        // First, we send the metadata associated with each worktree.
+        response.send(proto::JoinProjectResponse {
+            worktrees: worktrees.clone(),
+            replica_id: replica_id as u32,
+            collaborators: collaborators.clone(),
+            language_servers: project.language_servers.clone(),
+        })?;
 
-            let peer_count = project.guests.len();
-            let mut collaborators = Vec::with_capacity(peer_count);
-            collaborators.push(proto::Collaborator {
-                peer_id: project.host_connection_id.0,
-                replica_id: 0,
-                user_id: project.host.user_id.to_proto(),
-            });
-            let worktrees = project
-                .worktrees
-                .iter()
-                .map(|(id, worktree)| proto::WorktreeMetadata {
-                    id: *id,
-                    root_name: worktree.root_name.clone(),
-                    visible: worktree.visible,
-                })
-                .collect::<Vec<_>>();
-
-            // Add all guests other than the requesting user's own connections as collaborators
-            for (guest_conn_id, guest) in &project.guests {
-                if receipts_with_replica_ids
-                    .iter()
-                    .all(|(receipt, _)| receipt.sender_id != *guest_conn_id)
-                {
-                    collaborators.push(proto::Collaborator {
-                        peer_id: guest_conn_id.0,
-                        replica_id: guest.replica_id as u32,
-                        user_id: guest.user_id.to_proto(),
-                    });
-                }
-            }
+        for (worktree_id, worktree) in &project.worktrees {
+            #[cfg(any(test, feature = "test-support"))]
+            const MAX_CHUNK_SIZE: usize = 2;
+            #[cfg(not(any(test, feature = "test-support")))]
+            const MAX_CHUNK_SIZE: usize = 256;
 
-            for conn_id in project.connection_ids() {
-                for (receipt, replica_id) in &receipts_with_replica_ids {
-                    if conn_id != receipt.sender_id {
-                        self.peer.send(
-                            conn_id,
-                            proto::AddProjectCollaborator {
-                                project_id: project_id.to_proto(),
-                                collaborator: Some(proto::Collaborator {
-                                    peer_id: receipt.sender_id.0,
-                                    replica_id: *replica_id as u32,
-                                    user_id: guest_user_id.to_proto(),
-                                }),
-                            },
-                        )?;
-                    }
-                }
+            // Stream this worktree's entries.
+            let message = proto::UpdateWorktree {
+                project_id: project_id.to_proto(),
+                worktree_id: *worktree_id,
+                root_name: worktree.root_name.clone(),
+                updated_entries: worktree.entries.values().cloned().collect(),
+                removed_entries: Default::default(),
+                scan_id: worktree.scan_id,
+                is_last_update: worktree.is_complete,
+            };
+            for update in proto::split_worktree_update(message, MAX_CHUNK_SIZE) {
+                self.peer.send(request.sender_id, update.clone())?;
             }
 
-            // First, we send the metadata associated with each worktree.
-            for (receipt, replica_id) in &receipts_with_replica_ids {
-                self.peer.respond(
-                    *receipt,
-                    proto::JoinProjectResponse {
-                        variant: Some(proto::join_project_response::Variant::Accept(
-                            proto::join_project_response::Accept {
-                                worktrees: worktrees.clone(),
-                                replica_id: *replica_id as u32,
-                                collaborators: collaborators.clone(),
-                                language_servers: project.language_servers.clone(),
-                            },
-                        )),
+            // Stream this worktree's diagnostics.
+            for summary in worktree.diagnostic_summaries.values() {
+                self.peer.send(
+                    request.sender_id,
+                    proto::UpdateDiagnosticSummary {
+                        project_id: project_id.to_proto(),
+                        worktree_id: *worktree_id,
+                        summary: Some(summary.clone()),
                     },
                 )?;
             }
-
-            for (worktree_id, worktree) in &project.worktrees {
-                #[cfg(any(test, feature = "test-support"))]
-                const MAX_CHUNK_SIZE: usize = 2;
-                #[cfg(not(any(test, feature = "test-support")))]
-                const MAX_CHUNK_SIZE: usize = 256;
-
-                // Stream this worktree's entries.
-                let message = proto::UpdateWorktree {
-                    project_id: project_id.to_proto(),
-                    worktree_id: *worktree_id,
-                    root_name: worktree.root_name.clone(),
-                    updated_entries: worktree.entries.values().cloned().collect(),
-                    removed_entries: Default::default(),
-                    scan_id: worktree.scan_id,
-                    is_last_update: worktree.is_complete,
-                };
-                for update in proto::split_worktree_update(message, MAX_CHUNK_SIZE) {
-                    for (receipt, _) in &receipts_with_replica_ids {
-                        self.peer.send(receipt.sender_id, update.clone())?;
-                    }
-                }
-
-                // Stream this worktree's diagnostics.
-                for summary in worktree.diagnostic_summaries.values() {
-                    for (receipt, _) in &receipts_with_replica_ids {
-                        self.peer.send(
-                            receipt.sender_id,
-                            proto::UpdateDiagnosticSummary {
-                                project_id: project_id.to_proto(),
-                                worktree_id: *worktree_id,
-                                summary: Some(summary.clone()),
-                            },
-                        )?;
-                    }
-                }
-            }
         }
 
-        self.update_user_contacts(host_user_id).await?;
         Ok(())
     }
 
@@ -1041,27 +929,8 @@ impl Server {
                     )
                 });
             }
-
-            if let Some(requester_id) = project.cancel_request {
-                self.peer.send(
-                    project.host_connection_id,
-                    proto::JoinProjectRequestCancelled {
-                        project_id: project_id.to_proto(),
-                        requester_id: requester_id.to_proto(),
-                    },
-                )?;
-            }
-
-            if project.unshare {
-                self.peer.send(
-                    project.host_connection_id,
-                    proto::ProjectUnshared {
-                        project_id: project_id.to_proto(),
-                    },
-                )?;
-            }
         }
-        self.update_user_contacts(project.host_user_id).await?;
+
         Ok(())
     }
 
@@ -1070,61 +939,18 @@ impl Server {
         request: TypedEnvelope<proto::UpdateProject>,
     ) -> Result<()> {
         let project_id = ProjectId::from_proto(request.payload.project_id);
-        let user_id;
         {
             let mut state = self.store().await;
-            user_id = state.user_id_for_connection(request.sender_id)?;
             let guest_connection_ids = state
                 .read_project(project_id, request.sender_id)?
                 .guest_connection_ids();
-            let unshared_project = state.update_project(
-                project_id,
-                &request.payload.worktrees,
-                request.payload.online,
-                request.sender_id,
-            )?;
-
-            if let Some(unshared_project) = unshared_project {
-                broadcast(
-                    request.sender_id,
-                    unshared_project.guests.keys().copied(),
-                    |conn_id| {
-                        self.peer.send(
-                            conn_id,
-                            proto::UnregisterProject {
-                                project_id: project_id.to_proto(),
-                            },
-                        )
-                    },
-                );
-                for (_, receipts) in unshared_project.pending_join_requests {
-                    for receipt in receipts {
-                        self.peer.respond(
-                            receipt,
-                            proto::JoinProjectResponse {
-                                variant: Some(proto::join_project_response::Variant::Decline(
-                                    proto::join_project_response::Decline {
-                                        reason:
-                                            proto::join_project_response::decline::Reason::Closed
-                                                as i32,
-                                    },
-                                )),
-                            },
-                        )?;
-                    }
-                }
-            } else {
-                broadcast(request.sender_id, guest_connection_ids, |connection_id| {
-                    self.peer.forward_send(
-                        request.sender_id,
-                        connection_id,
-                        request.payload.clone(),
-                    )
-                });
-            }
+            state.update_project(project_id, &request.payload.worktrees, request.sender_id)?;
+            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(user_id).await?;
         Ok(())
     }
 
@@ -1146,32 +972,21 @@ impl Server {
     ) -> Result<()> {
         let project_id = ProjectId::from_proto(request.payload.project_id);
         let worktree_id = request.payload.worktree_id;
-        let (connection_ids, metadata_changed) = {
-            let mut store = self.store().await;
-            let (connection_ids, metadata_changed) = store.update_worktree(
-                request.sender_id,
-                project_id,
-                worktree_id,
-                &request.payload.root_name,
-                &request.payload.removed_entries,
-                &request.payload.updated_entries,
-                request.payload.scan_id,
-                request.payload.is_last_update,
-            )?;
-            (connection_ids, metadata_changed)
-        };
+        let connection_ids = self.store().await.update_worktree(
+            request.sender_id,
+            project_id,
+            worktree_id,
+            &request.payload.root_name,
+            &request.payload.removed_entries,
+            &request.payload.updated_entries,
+            request.payload.scan_id,
+            request.payload.is_last_update,
+        )?;
 
         broadcast(request.sender_id, connection_ids, |connection_id| {
             self.peer
                 .forward_send(request.sender_id, connection_id, request.payload.clone())
         });
-        if metadata_changed {
-            let user_id = self
-                .store()
-                .await
-                .user_id_for_connection(request.sender_id)?;
-            self.update_user_contacts(user_id).await?;
-        }
         response.send(proto::Ack {})?;
         Ok(())
     }

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

@@ -1,7 +1,7 @@
 use crate::db::{self, ChannelId, ProjectId, UserId};
 use anyhow::{anyhow, Result};
-use collections::{btree_map, hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet};
-use rpc::{proto, ConnectionId, Receipt};
+use collections::{btree_map, BTreeMap, BTreeSet, HashMap, HashSet};
+use rpc::{proto, ConnectionId};
 use serde::Serialize;
 use std::{mem, path::PathBuf, str, time::Duration};
 use time::OffsetDateTime;
@@ -32,7 +32,6 @@ struct ConnectionState {
     user_id: UserId,
     admin: bool,
     projects: BTreeSet<ProjectId>,
-    requested_projects: HashSet<ProjectId>,
     channels: HashSet<ChannelId>,
 }
 
@@ -45,12 +44,9 @@ pub struct Call {
 
 #[derive(Serialize)]
 pub struct Project {
-    pub online: bool,
     pub host_connection_id: ConnectionId,
     pub host: Collaborator,
     pub guests: HashMap<ConnectionId, Collaborator>,
-    #[serde(skip)]
-    pub join_requests: HashMap<UserId, Vec<Receipt<proto::JoinProject>>>,
     pub active_replica_ids: HashSet<ReplicaId>,
     pub worktrees: BTreeMap<u64, Worktree>,
     pub language_servers: Vec<proto::LanguageServer>,
@@ -98,13 +94,10 @@ pub struct LeftProject {
     pub host_connection_id: ConnectionId,
     pub connection_ids: Vec<ConnectionId>,
     pub remove_collaborator: bool,
-    pub cancel_request: Option<UserId>,
-    pub unshare: bool,
 }
 
 pub struct UnsharedProject {
-    pub guests: HashMap<ConnectionId, Collaborator>,
-    pub pending_join_requests: HashMap<UserId, Vec<Receipt<proto::JoinProject>>>,
+    pub guest_connection_ids: Vec<ConnectionId>,
 }
 
 #[derive(Copy, Clone)]
@@ -159,7 +152,6 @@ impl Store {
                 user_id,
                 admin,
                 projects: Default::default(),
-                requested_projects: Default::default(),
                 channels: Default::default(),
             },
         );
@@ -578,7 +570,6 @@ impl Store {
         &mut self,
         host_connection_id: ConnectionId,
         project_id: ProjectId,
-        online: bool,
     ) -> Result<()> {
         let connection = self
             .connections
@@ -588,7 +579,6 @@ impl Store {
         self.projects.insert(
             project_id,
             Project {
-                online,
                 host_connection_id,
                 host: Collaborator {
                     user_id: connection.user_id,
@@ -597,7 +587,6 @@ impl Store {
                     admin: connection.admin,
                 },
                 guests: Default::default(),
-                join_requests: Default::default(),
                 active_replica_ids: Default::default(),
                 worktrees: Default::default(),
                 language_servers: Default::default(),
@@ -610,9 +599,8 @@ impl Store {
         &mut self,
         project_id: ProjectId,
         worktrees: &[proto::WorktreeMetadata],
-        online: bool,
         connection_id: ConnectionId,
-    ) -> Result<Option<UnsharedProject>> {
+    ) -> Result<()> {
         let project = self
             .projects
             .get_mut(&project_id)
@@ -634,32 +622,7 @@ impl Store {
                 }
             }
 
-            if online != project.online {
-                project.online = online;
-                if project.online {
-                    Ok(None)
-                } else {
-                    for connection_id in project.guest_connection_ids() {
-                        if let Some(connection) = self.connections.get_mut(&connection_id) {
-                            connection.projects.remove(&project_id);
-                        }
-                    }
-
-                    project.active_replica_ids.clear();
-                    project.language_servers.clear();
-                    for worktree in project.worktrees.values_mut() {
-                        worktree.diagnostic_summaries.clear();
-                        worktree.entries.clear();
-                    }
-
-                    Ok(Some(UnsharedProject {
-                        guests: mem::take(&mut project.guests),
-                        pending_join_requests: mem::take(&mut project.join_requests),
-                    }))
-                }
-            } else {
-                Ok(None)
-            }
+            Ok(())
         } else {
             Err(anyhow!("no such project"))?
         }
@@ -685,22 +648,6 @@ impl Store {
                         }
                     }
 
-                    for requester_user_id in project.join_requests.keys() {
-                        if let Some(requester_user_connection_state) =
-                            self.connected_users.get_mut(requester_user_id)
-                        {
-                            for requester_connection_id in
-                                &requester_user_connection_state.connection_ids
-                            {
-                                if let Some(requester_connection) =
-                                    self.connections.get_mut(requester_connection_id)
-                                {
-                                    requester_connection.requested_projects.remove(&project_id);
-                                }
-                            }
-                        }
-                    }
-
                     Ok(project)
                 } else {
                     Err(anyhow!("no such project"))?
@@ -710,6 +657,37 @@ impl Store {
         }
     }
 
+    pub fn unshare_project(
+        &mut self,
+        project_id: ProjectId,
+        connection_id: ConnectionId,
+    ) -> Result<UnsharedProject> {
+        let project = self
+            .projects
+            .get_mut(&project_id)
+            .ok_or_else(|| anyhow!("no such project"))?;
+        anyhow::ensure!(
+            project.host_connection_id == connection_id,
+            "no such project"
+        );
+
+        let guest_connection_ids = project.guest_connection_ids();
+        project.active_replica_ids.clear();
+        project.guests.clear();
+        project.language_servers.clear();
+        project.worktrees.clear();
+
+        for connection_id in &guest_connection_ids {
+            if let Some(connection) = self.connections.get_mut(connection_id) {
+                connection.projects.remove(&project_id);
+            }
+        }
+
+        Ok(UnsharedProject {
+            guest_connection_ids,
+        })
+    }
+
     pub fn update_diagnostic_summary(
         &mut self,
         project_id: ProjectId,
@@ -753,91 +731,37 @@ impl Store {
         Err(anyhow!("no such project"))?
     }
 
-    pub fn request_join_project(
+    pub fn join_project(
         &mut self,
-        requester_id: UserId,
+        requester_connection_id: ConnectionId,
         project_id: ProjectId,
-        receipt: Receipt<proto::JoinProject>,
-    ) -> Result<()> {
+    ) -> Result<(&Project, ReplicaId)> {
         let connection = self
             .connections
-            .get_mut(&receipt.sender_id)
+            .get_mut(&requester_connection_id)
             .ok_or_else(|| anyhow!("no such connection"))?;
         let project = self
             .projects
             .get_mut(&project_id)
             .ok_or_else(|| anyhow!("no such project"))?;
-        if project.online {
-            connection.requested_projects.insert(project_id);
-            project
-                .join_requests
-                .entry(requester_id)
-                .or_default()
-                .push(receipt);
-            Ok(())
-        } else {
-            Err(anyhow!("no such project"))
-        }
-    }
-
-    pub fn deny_join_project_request(
-        &mut self,
-        responder_connection_id: ConnectionId,
-        requester_id: UserId,
-        project_id: ProjectId,
-    ) -> Option<Vec<Receipt<proto::JoinProject>>> {
-        let project = self.projects.get_mut(&project_id)?;
-        if responder_connection_id != project.host_connection_id {
-            return None;
-        }
-
-        let receipts = project.join_requests.remove(&requester_id)?;
-        for receipt in &receipts {
-            let requester_connection = self.connections.get_mut(&receipt.sender_id)?;
-            requester_connection.requested_projects.remove(&project_id);
-        }
-        project.host.last_activity = Some(OffsetDateTime::now_utc());
-
-        Some(receipts)
-    }
-
-    #[allow(clippy::type_complexity)]
-    pub fn accept_join_project_request(
-        &mut self,
-        responder_connection_id: ConnectionId,
-        requester_id: UserId,
-        project_id: ProjectId,
-    ) -> Option<(Vec<(Receipt<proto::JoinProject>, ReplicaId)>, &Project)> {
-        let project = self.projects.get_mut(&project_id)?;
-        if responder_connection_id != project.host_connection_id {
-            return None;
-        }
-
-        let receipts = project.join_requests.remove(&requester_id)?;
-        let mut receipts_with_replica_ids = Vec::new();
-        for receipt in receipts {
-            let requester_connection = self.connections.get_mut(&receipt.sender_id)?;
-            requester_connection.requested_projects.remove(&project_id);
-            requester_connection.projects.insert(project_id);
-            let mut replica_id = 1;
-            while project.active_replica_ids.contains(&replica_id) {
-                replica_id += 1;
-            }
-            project.active_replica_ids.insert(replica_id);
-            project.guests.insert(
-                receipt.sender_id,
-                Collaborator {
-                    replica_id,
-                    user_id: requester_id,
-                    last_activity: Some(OffsetDateTime::now_utc()),
-                    admin: requester_connection.admin,
-                },
-            );
-            receipts_with_replica_ids.push((receipt, replica_id));
-        }
+        connection.projects.insert(project_id);
+        let mut replica_id = 1;
+        while project.active_replica_ids.contains(&replica_id) {
+            replica_id += 1;
+        }
+        project.active_replica_ids.insert(replica_id);
+        project.guests.insert(
+            requester_connection_id,
+            Collaborator {
+                replica_id,
+                user_id: connection.user_id,
+                last_activity: Some(OffsetDateTime::now_utc()),
+                admin: connection.admin,
+            },
+        );
 
         project.host.last_activity = Some(OffsetDateTime::now_utc());
-        Some((receipts_with_replica_ids, project))
+        Ok((project, replica_id))
     }
 
     pub fn leave_project(
@@ -845,7 +769,6 @@ impl Store {
         connection_id: ConnectionId,
         project_id: ProjectId,
     ) -> Result<LeftProject> {
-        let user_id = self.user_id_for_connection(connection_id)?;
         let project = self
             .projects
             .get_mut(&project_id)
@@ -859,39 +782,14 @@ impl Store {
             false
         };
 
-        // If the connection leaving the project has a pending request, remove it.
-        // If that user has no other pending requests on other connections, indicate that the request should be cancelled.
-        let mut cancel_request = None;
-        if let Entry::Occupied(mut entry) = project.join_requests.entry(user_id) {
-            entry
-                .get_mut()
-                .retain(|receipt| receipt.sender_id != connection_id);
-            if entry.get().is_empty() {
-                entry.remove();
-                cancel_request = Some(user_id);
-            }
-        }
-
         if let Some(connection) = self.connections.get_mut(&connection_id) {
             connection.projects.remove(&project_id);
         }
 
-        let connection_ids = project.connection_ids();
-        let unshare = connection_ids.len() <= 1 && project.join_requests.is_empty();
-        if unshare {
-            project.language_servers.clear();
-            for worktree in project.worktrees.values_mut() {
-                worktree.diagnostic_summaries.clear();
-                worktree.entries.clear();
-            }
-        }
-
         Ok(LeftProject {
             host_connection_id: project.host_connection_id,
             host_user_id: project.host.user_id,
-            connection_ids,
-            cancel_request,
-            unshare,
+            connection_ids: project.connection_ids(),
             remove_collaborator,
         })
     }
@@ -907,15 +805,11 @@ impl Store {
         updated_entries: &[proto::Entry],
         scan_id: u64,
         is_last_update: bool,
-    ) -> Result<(Vec<ConnectionId>, bool)> {
+    ) -> Result<Vec<ConnectionId>> {
         let project = self.write_project(project_id, connection_id)?;
-        if !project.online {
-            return Err(anyhow!("project is not online"));
-        }
 
         let connection_ids = project.connection_ids();
         let mut worktree = project.worktrees.entry(worktree_id).or_default();
-        let metadata_changed = worktree_root_name != worktree.root_name;
         worktree.root_name = worktree_root_name.to_string();
 
         for entry_id in removed_entries {
@@ -928,7 +822,7 @@ impl Store {
 
         worktree.scan_id = scan_id;
         worktree.is_complete = is_last_update;
-        Ok((connection_ids, metadata_changed))
+        Ok(connection_ids)
     }
 
     pub fn project_connection_ids(

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -1,6 +1,5 @@
 mod contact_finder;
 mod contact_notification;
-mod join_project_notification;
 mod notifications;
 
 use client::{Contact, ContactEventKind, User, UserStore};
@@ -13,9 +12,7 @@ use gpui::{
     MouseButton, MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle,
     WeakViewHandle,
 };
-use join_project_notification::JoinProjectNotification;
 use menu::{Confirm, SelectNext, SelectPrev};
-use project::ProjectStore;
 use serde::Deserialize;
 use settings::Settings;
 use std::sync::Arc;
@@ -54,7 +51,6 @@ pub struct ContactsPanel {
     match_candidates: Vec<StringMatchCandidate>,
     list_state: ListState,
     user_store: ModelHandle<UserStore>,
-    project_store: ModelHandle<ProjectStore>,
     filter_editor: ViewHandle<Editor>,
     collapsed_sections: Vec<Section>,
     selection: Option<usize>,
@@ -76,7 +72,6 @@ pub struct RespondToContactRequest {
 pub fn init(cx: &mut MutableAppContext) {
     contact_finder::init(cx);
     contact_notification::init(cx);
-    join_project_notification::init(cx);
     cx.add_action(ContactsPanel::request_contact);
     cx.add_action(ContactsPanel::remove_contact);
     cx.add_action(ContactsPanel::respond_to_contact_request);
@@ -90,7 +85,6 @@ pub fn init(cx: &mut MutableAppContext) {
 impl ContactsPanel {
     pub fn new(
         user_store: ModelHandle<UserStore>,
-        project_store: ModelHandle<ProjectStore>,
         workspace: WeakViewHandle<Workspace>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
@@ -120,38 +114,6 @@ impl ContactsPanel {
         })
         .detach();
 
-        cx.defer({
-            let workspace = workspace.clone();
-            move |_, cx| {
-                if let Some(workspace_handle) = workspace.upgrade(cx) {
-                    cx.subscribe(&workspace_handle.read(cx).project().clone(), {
-                        let workspace = workspace;
-                        move |_, project, event, cx| {
-                            if let project::Event::ContactRequestedJoin(user) = event {
-                                if let Some(workspace) = workspace.upgrade(cx) {
-                                    workspace.update(cx, |workspace, cx| {
-                                        workspace.show_notification(user.id as usize, cx, |cx| {
-                                            cx.add_view(|cx| {
-                                                JoinProjectNotification::new(
-                                                    project,
-                                                    user.clone(),
-                                                    cx,
-                                                )
-                                            })
-                                        })
-                                    });
-                                }
-                            }
-                        }
-                    })
-                    .detach();
-                }
-            }
-        });
-
-        cx.observe(&project_store, |this, _, cx| this.update_entries(cx))
-            .detach();
-
         cx.subscribe(&user_store, move |_, user_store, event, cx| {
             if let Some(workspace) = workspace.upgrade(cx) {
                 workspace.update(cx, |workspace, cx| {
@@ -219,7 +181,6 @@ impl ContactsPanel {
             filter_editor,
             _maintain_contacts: cx.observe(&user_store, |this, _, cx| this.update_entries(cx)),
             user_store,
-            project_store,
         };
         this.update_entries(cx);
         this
@@ -841,7 +802,7 @@ mod tests {
     use collections::HashSet;
     use gpui::TestAppContext;
     use language::LanguageRegistry;
-    use project::{FakeFs, Project};
+    use project::{FakeFs, Project, ProjectStore};
 
     #[gpui::test]
     async fn test_contact_panel(cx: &mut TestAppContext) {
@@ -852,12 +813,11 @@ mod tests {
         let http_client = FakeHttpClient::with_404_response();
         let client = Client::new(http_client.clone());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
-        let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
+        let project_store = cx.add_model(|_| ProjectStore::new());
         let server = FakeServer::for_client(current_user_id, &client, cx).await;
         let fs = FakeFs::new(cx.background());
         let project = cx.update(|cx| {
             Project::local(
-                false,
                 client.clone(),
                 user_store.clone(),
                 project_store.clone(),
@@ -870,12 +830,7 @@ mod tests {
         let (_, workspace) =
             cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx));
         let panel = cx.add_view(&workspace, |cx| {
-            ContactsPanel::new(
-                user_store.clone(),
-                project_store.clone(),
-                workspace.downgrade(),
-                cx,
-            )
+            ContactsPanel::new(user_store.clone(), workspace.downgrade(), cx)
         });
 
         workspace.update(cx, |_, cx| {
@@ -890,6 +845,14 @@ mod tests {
             .detach();
         });
 
+        let request = server.receive::<proto::RegisterProject>().await.unwrap();
+        server
+            .respond(
+                request.receipt(),
+                proto::RegisterProjectResponse { project_id: 200 },
+            )
+            .await;
+
         let get_users_request = server.receive::<proto::GetUsers>().await.unwrap();
         server
             .respond(
@@ -920,14 +883,6 @@ mod tests {
             )
             .await;
 
-        let request = server.receive::<proto::RegisterProject>().await.unwrap();
-        server
-            .respond(
-                request.receipt(),
-                proto::RegisterProjectResponse { project_id: 200 },
-            )
-            .await;
-
         server.send(proto::UpdateContacts {
             incoming_requests: vec![proto::IncomingContactRequest {
                 requester_id: 1,

crates/contacts_panel/src/join_project_notification.rs 🔗

@@ -1,80 +0,0 @@
-use client::User;
-use gpui::{
-    actions, ElementBox, Entity, ModelHandle, MutableAppContext, RenderContext, View, ViewContext,
-};
-use project::Project;
-use std::sync::Arc;
-use workspace::Notification;
-
-use crate::notifications::render_user_notification;
-
-pub fn init(cx: &mut MutableAppContext) {
-    cx.add_action(JoinProjectNotification::decline);
-    cx.add_action(JoinProjectNotification::accept);
-}
-
-pub enum Event {
-    Dismiss,
-}
-
-actions!(contacts_panel, [Accept, Decline]);
-
-pub struct JoinProjectNotification {
-    project: ModelHandle<Project>,
-    user: Arc<User>,
-}
-
-impl JoinProjectNotification {
-    pub fn new(project: ModelHandle<Project>, user: Arc<User>, cx: &mut ViewContext<Self>) -> Self {
-        cx.subscribe(&project, |this, _, event, cx| {
-            if let project::Event::ContactCancelledJoinRequest(user) = event {
-                if *user == this.user {
-                    cx.emit(Event::Dismiss);
-                }
-            }
-        })
-        .detach();
-        Self { project, user }
-    }
-
-    fn decline(&mut self, _: &Decline, cx: &mut ViewContext<Self>) {
-        self.project.update(cx, |project, cx| {
-            project.respond_to_join_request(self.user.id, false, cx)
-        });
-        cx.emit(Event::Dismiss)
-    }
-
-    fn accept(&mut self, _: &Accept, cx: &mut ViewContext<Self>) {
-        self.project.update(cx, |project, cx| {
-            project.respond_to_join_request(self.user.id, true, cx)
-        });
-        cx.emit(Event::Dismiss)
-    }
-}
-
-impl Entity for JoinProjectNotification {
-    type Event = Event;
-}
-
-impl View for JoinProjectNotification {
-    fn ui_name() -> &'static str {
-        "JoinProjectNotification"
-    }
-
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        render_user_notification(
-            self.user.clone(),
-            "wants to join your project",
-            None,
-            Decline,
-            vec![("Decline", Box::new(Decline)), ("Accept", Box::new(Accept))],
-            cx,
-        )
-    }
-}
-
-impl Notification for JoinProjectNotification {
-    fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool {
-        matches!(event, Event::Dismiss)
-    }
-}

crates/project/src/project.rs 🔗

@@ -74,7 +74,6 @@ pub trait Item: Entity {
 }
 
 pub struct ProjectStore {
-    db: Arc<Db>,
     projects: Vec<WeakModelHandle<Project>>,
 }
 
@@ -126,7 +125,6 @@ pub struct Project {
     incomplete_buffers: HashMap<u64, ModelHandle<Buffer>>,
     buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>,
     nonce: u128,
-    initialized_persistent_state: bool,
     _maintain_buffer_languages: Task<()>,
 }
 
@@ -158,10 +156,7 @@ enum ProjectClientState {
         is_shared: bool,
         remote_id_tx: watch::Sender<Option<u64>>,
         remote_id_rx: watch::Receiver<Option<u64>>,
-        online_tx: watch::Sender<bool>,
-        online_rx: watch::Receiver<bool>,
         _maintain_remote_id: Task<Option<()>>,
-        _maintain_online_status: Task<Option<()>>,
     },
     Remote {
         sharing_has_stopped: bool,
@@ -196,8 +191,6 @@ pub enum Event {
     RemoteIdChanged(Option<u64>),
     DisconnectedFromHost,
     CollaboratorLeft(PeerId),
-    ContactRequestedJoin(Arc<User>),
-    ContactCancelledJoinRequest(Arc<User>),
 }
 
 pub enum LanguageServerState {
@@ -382,17 +375,15 @@ impl FormatTrigger {
 
 impl Project {
     pub fn init(client: &Arc<Client>) {
-        client.add_model_message_handler(Self::handle_request_join_project);
         client.add_model_message_handler(Self::handle_add_collaborator);
         client.add_model_message_handler(Self::handle_buffer_reloaded);
         client.add_model_message_handler(Self::handle_buffer_saved);
         client.add_model_message_handler(Self::handle_start_language_server);
         client.add_model_message_handler(Self::handle_update_language_server);
         client.add_model_message_handler(Self::handle_remove_collaborator);
-        client.add_model_message_handler(Self::handle_join_project_request_cancelled);
         client.add_model_message_handler(Self::handle_update_project);
         client.add_model_message_handler(Self::handle_unregister_project);
-        client.add_model_message_handler(Self::handle_project_unshared);
+        client.add_model_message_handler(Self::handle_unshare_project);
         client.add_model_message_handler(Self::handle_create_buffer_for_peer);
         client.add_model_message_handler(Self::handle_update_buffer_file);
         client.add_model_message_handler(Self::handle_update_buffer);
@@ -424,7 +415,6 @@ impl Project {
     }
 
     pub fn local(
-        online: bool,
         client: Arc<Client>,
         user_store: ModelHandle<UserStore>,
         project_store: ModelHandle<ProjectStore>,
@@ -453,23 +443,6 @@ impl Project {
                 }
             });
 
-            let (online_tx, online_rx) = watch::channel_with(online);
-            let _maintain_online_status = cx.spawn_weak({
-                let mut online_rx = online_rx.clone();
-                move |this, mut cx| async move {
-                    while let Some(online) = online_rx.recv().await {
-                        let this = this.upgrade(&cx)?;
-                        this.update(&mut cx, |this, cx| {
-                            if !online {
-                                this.unshared(cx);
-                            }
-                            this.metadata_changed(false, cx)
-                        });
-                    }
-                    None
-                }
-            });
-
             let handle = cx.weak_handle();
             project_store.update(cx, |store, cx| store.add_project(handle, cx));
 
@@ -486,10 +459,7 @@ impl Project {
                     is_shared: false,
                     remote_id_tx,
                     remote_id_rx,
-                    online_tx,
-                    online_rx,
                     _maintain_remote_id,
-                    _maintain_online_status,
                 },
                 opened_buffer: watch::channel(),
                 client_subscriptions: Vec::new(),
@@ -510,7 +480,6 @@ impl Project {
                 language_server_settings: Default::default(),
                 next_language_server_id: 0,
                 nonce: StdRng::from_entropy().gen(),
-                initialized_persistent_state: false,
             }
         })
     }
@@ -532,24 +501,6 @@ impl Project {
             })
             .await?;
 
-        let response = match response.variant.ok_or_else(|| anyhow!("missing variant"))? {
-            proto::join_project_response::Variant::Accept(response) => response,
-            proto::join_project_response::Variant::Decline(decline) => {
-                match proto::join_project_response::decline::Reason::from_i32(decline.reason) {
-                    Some(proto::join_project_response::decline::Reason::Declined) => {
-                        Err(JoinProjectError::HostDeclined)?
-                    }
-                    Some(proto::join_project_response::decline::Reason::Closed) => {
-                        Err(JoinProjectError::HostClosedProject)?
-                    }
-                    Some(proto::join_project_response::decline::Reason::WentOffline) => {
-                        Err(JoinProjectError::HostWentOffline)?
-                    }
-                    None => Err(anyhow!("missing decline reason"))?,
-                }
-            }
-        };
-
         let replica_id = response.replica_id as ReplicaId;
 
         let mut worktrees = Vec::new();
@@ -625,7 +576,6 @@ impl Project {
                 opened_buffers: Default::default(),
                 buffer_snapshots: Default::default(),
                 nonce: StdRng::from_entropy().gen(),
-                initialized_persistent_state: false,
             };
             for worktree in worktrees {
                 this.add_worktree(&worktree, cx);
@@ -668,10 +618,9 @@ impl Project {
         let http_client = client::test::FakeHttpClient::with_404_response();
         let client = client::Client::new(http_client.clone());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
-        let project_store = cx.add_model(|_| ProjectStore::new(Db::open_fake()));
-        let project = cx.update(|cx| {
-            Project::local(true, client, user_store, project_store, languages, fs, cx)
-        });
+        let project_store = cx.add_model(|_| ProjectStore::new());
+        let project =
+            cx.update(|cx| Project::local(client, user_store, project_store, languages, fs, cx));
         for path in root_paths {
             let (tree, _) = project
                 .update(cx, |project, cx| {
@@ -685,53 +634,6 @@ impl Project {
         project
     }
 
-    pub fn restore_state(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        if self.is_remote() {
-            return Task::ready(Ok(()));
-        }
-
-        let db = self.project_store.read(cx).db.clone();
-        let keys = self.db_keys_for_online_state(cx);
-        let online_by_default = cx.global::<Settings>().projects_online_by_default;
-        let read_online = cx.background().spawn(async move {
-            let values = db.read(keys)?;
-            anyhow::Ok(
-                values
-                    .into_iter()
-                    .all(|e| e.map_or(online_by_default, |e| e == [true as u8])),
-            )
-        });
-        cx.spawn(|this, mut cx| async move {
-            let online = read_online.await.log_err().unwrap_or(false);
-            this.update(&mut cx, |this, cx| {
-                this.initialized_persistent_state = true;
-                if let ProjectClientState::Local { online_tx, .. } = &mut this.client_state {
-                    let mut online_tx = online_tx.borrow_mut();
-                    if *online_tx != online {
-                        *online_tx = online;
-                        drop(online_tx);
-                        this.metadata_changed(false, cx);
-                    }
-                }
-            });
-            Ok(())
-        })
-    }
-
-    fn persist_state(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        if self.is_remote() || !self.initialized_persistent_state {
-            return Task::ready(Ok(()));
-        }
-
-        let db = self.project_store.read(cx).db.clone();
-        let keys = self.db_keys_for_online_state(cx);
-        let is_online = self.is_online();
-        cx.background().spawn(async move {
-            let value = &[is_online as u8];
-            db.write(keys.into_iter().map(|key| (key, value)))
-        })
-    }
-
     fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
         let settings = cx.global::<Settings>();
 
@@ -860,24 +762,8 @@ impl Project {
         &self.fs
     }
 
-    pub fn set_online(&mut self, online: bool, _: &mut ModelContext<Self>) {
-        if let ProjectClientState::Local { online_tx, .. } = &mut self.client_state {
-            let mut online_tx = online_tx.borrow_mut();
-            if *online_tx != online {
-                *online_tx = online;
-            }
-        }
-    }
-
-    pub fn is_online(&self) -> bool {
-        match &self.client_state {
-            ProjectClientState::Local { online_rx, .. } => *online_rx.borrow(),
-            ProjectClientState::Remote { .. } => true,
-        }
-    }
-
     fn unregister(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        self.unshared(cx);
+        self.unshare(cx).log_err();
         if let ProjectClientState::Local { remote_id_rx, .. } = &mut self.client_state {
             if let Some(remote_id) = *remote_id_rx.borrow() {
                 let request = self.client.request(proto::UnregisterProject {
@@ -905,7 +791,7 @@ impl Project {
                             *remote_id_tx.borrow_mut() = None;
                         }
                         this.client_subscriptions.clear();
-                        this.metadata_changed(false, cx);
+                        this.metadata_changed(cx);
                     });
                     response.map(drop)
                 });
@@ -915,19 +801,12 @@ impl Project {
     }
 
     fn register(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        if let ProjectClientState::Local {
-            remote_id_rx,
-            online_rx,
-            ..
-        } = &self.client_state
-        {
+        if let ProjectClientState::Local { remote_id_rx, .. } = &self.client_state {
             if remote_id_rx.borrow().is_some() {
                 return Task::ready(Ok(()));
             }
 
-            let response = self.client.request(proto::RegisterProject {
-                online: *online_rx.borrow(),
-            });
+            let response = self.client.request(proto::RegisterProject {});
             cx.spawn(|this, mut cx| async move {
                 let remote_id = response.await?.project_id;
                 this.update(&mut cx, |this, cx| {
@@ -935,7 +814,7 @@ impl Project {
                         *remote_id_tx.borrow_mut() = Some(remote_id);
                     }
 
-                    this.metadata_changed(false, cx);
+                    this.metadata_changed(cx);
                     cx.emit(Event::RemoteIdChanged(Some(remote_id)));
                     this.client_subscriptions
                         .push(this.client.add_model_for_remote_entity(remote_id, cx));
@@ -1001,65 +880,50 @@ impl Project {
         }
     }
 
-    fn metadata_changed(&mut self, persist: bool, cx: &mut ModelContext<Self>) {
-        if let ProjectClientState::Local {
-            remote_id_rx,
-            online_rx,
-            ..
-        } = &self.client_state
-        {
+    fn metadata_changed(&mut self, cx: &mut ModelContext<Self>) {
+        if let ProjectClientState::Local { remote_id_rx, .. } = &self.client_state {
             // Broadcast worktrees only if the project is online.
-            let worktrees = if *online_rx.borrow() {
-                self.worktrees
-                    .iter()
-                    .filter_map(|worktree| {
-                        worktree
-                            .upgrade(cx)
-                            .map(|worktree| worktree.read(cx).as_local().unwrap().metadata_proto())
-                    })
-                    .collect()
-            } else {
-                Default::default()
-            };
+            let worktrees = self
+                .worktrees
+                .iter()
+                .filter_map(|worktree| {
+                    worktree
+                        .upgrade(cx)
+                        .map(|worktree| worktree.read(cx).as_local().unwrap().metadata_proto())
+                })
+                .collect();
             if let Some(project_id) = *remote_id_rx.borrow() {
-                let online = *online_rx.borrow();
                 self.client
                     .send(proto::UpdateProject {
                         project_id,
                         worktrees,
-                        online,
                     })
                     .log_err();
 
-                if online {
-                    let worktrees = self.visible_worktrees(cx).collect::<Vec<_>>();
-                    let scans_complete =
-                        futures::future::join_all(worktrees.iter().filter_map(|worktree| {
-                            Some(worktree.read(cx).as_local()?.scan_complete())
-                        }));
+                let worktrees = self.visible_worktrees(cx).collect::<Vec<_>>();
+                let scans_complete =
+                    futures::future::join_all(worktrees.iter().filter_map(|worktree| {
+                        Some(worktree.read(cx).as_local()?.scan_complete())
+                    }));
 
-                    let worktrees = worktrees.into_iter().map(|handle| handle.downgrade());
-                    cx.spawn_weak(move |_, cx| async move {
-                        scans_complete.await;
-                        cx.read(|cx| {
-                            for worktree in worktrees {
-                                if let Some(worktree) = worktree
-                                    .upgrade(cx)
-                                    .and_then(|worktree| worktree.read(cx).as_local())
-                                {
-                                    worktree.send_extension_counts(project_id);
-                                }
+                let worktrees = worktrees.into_iter().map(|handle| handle.downgrade());
+                cx.spawn_weak(move |_, cx| async move {
+                    scans_complete.await;
+                    cx.read(|cx| {
+                        for worktree in worktrees {
+                            if let Some(worktree) = worktree
+                                .upgrade(cx)
+                                .and_then(|worktree| worktree.read(cx).as_local())
+                            {
+                                worktree.send_extension_counts(project_id);
                             }
-                        })
+                        }
                     })
-                    .detach();
-                }
+                })
+                .detach();
             }
 
             self.project_store.update(cx, |_, cx| cx.notify());
-            if persist {
-                self.persist_state(cx).detach_and_log_err(cx);
-            }
             cx.notify();
         }
     }
@@ -1097,23 +961,6 @@ impl Project {
             .map(|tree| tree.read(cx).root_name())
     }
 
-    fn db_keys_for_online_state(&self, cx: &AppContext) -> Vec<String> {
-        self.worktrees
-            .iter()
-            .filter_map(|worktree| {
-                let worktree = worktree.upgrade(cx)?.read(cx);
-                if worktree.is_visible() {
-                    Some(format!(
-                        "project-path-online:{}",
-                        worktree.as_local().unwrap().abs_path().to_string_lossy()
-                    ))
-                } else {
-                    None
-                }
-            })
-            .collect::<Vec<_>>()
-    }
-
     pub fn worktree_for_id(
         &self,
         id: WorktreeId,
@@ -1317,11 +1164,7 @@ impl Project {
         }
     }
 
-    fn share(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        if !self.is_online() {
-            return Task::ready(Err(anyhow!("can't share an offline project")));
-        }
-
+    pub fn share(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
         let project_id;
         if let ProjectClientState::Local {
             remote_id_rx,
@@ -1394,10 +1237,15 @@ impl Project {
         })
     }
 
-    fn unshared(&mut self, cx: &mut ModelContext<Self>) {
-        if let ProjectClientState::Local { is_shared, .. } = &mut self.client_state {
+    pub fn unshare(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
+        if let ProjectClientState::Local {
+            is_shared,
+            remote_id_rx,
+            ..
+        } = &mut self.client_state
+        {
             if !*is_shared {
-                return;
+                return Ok(());
             }
 
             *is_shared = false;
@@ -1422,37 +1270,13 @@ impl Project {
             }
 
             cx.notify();
-        } else {
-            log::error!("attempted to unshare a remote project");
-        }
-    }
+            if let Some(project_id) = *remote_id_rx.borrow() {
+                self.client.send(proto::UnshareProject { project_id })?;
+            }
 
-    pub fn respond_to_join_request(
-        &mut self,
-        requester_id: u64,
-        allow: bool,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(project_id) = self.remote_id() {
-            let share = if self.is_online() && allow {
-                Some(self.share(cx))
-            } else {
-                None
-            };
-            let client = self.client.clone();
-            cx.foreground()
-                .spawn(async move {
-                    client.send(proto::RespondToJoinProjectRequest {
-                        requester_id,
-                        project_id,
-                        allow,
-                    })?;
-                    if let Some(share) = share {
-                        share.await?;
-                    }
-                    anyhow::Ok(())
-                })
-                .detach_and_log_err(cx);
+            Ok(())
+        } else {
+            Err(anyhow!("attempted to unshare a remote project"))
         }
     }
 
@@ -4527,7 +4351,7 @@ impl Project {
                 false
             }
         });
-        self.metadata_changed(true, cx);
+        self.metadata_changed(cx);
         cx.notify();
     }
 
@@ -4552,7 +4376,7 @@ impl Project {
                 .push(WorktreeHandle::Weak(worktree.downgrade()));
         }
 
-        self.metadata_changed(true, cx);
+        self.metadata_changed(cx);
         cx.observe_release(worktree, |this, worktree, cx| {
             this.remove_worktree(worktree.id(), cx);
             cx.notify();
@@ -4728,29 +4552,6 @@ impl Project {
 
     // RPC message handlers
 
-    async fn handle_request_join_project(
-        this: ModelHandle<Self>,
-        message: TypedEnvelope<proto::RequestJoinProject>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let user_id = message.payload.requester_id;
-        if this.read_with(&cx, |project, _| {
-            project.collaborators.values().any(|c| c.user.id == user_id)
-        }) {
-            this.update(&mut cx, |this, cx| {
-                this.respond_to_join_request(user_id, true, cx)
-            });
-        } else {
-            let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
-            let user = user_store
-                .update(&mut cx, |store, cx| store.get_user(user_id, cx))
-                .await?;
-            this.update(&mut cx, |_, cx| cx.emit(Event::ContactRequestedJoin(user)));
-        }
-        Ok(())
-    }
-
     async fn handle_unregister_project(
         this: ModelHandle<Self>,
         _: TypedEnvelope<proto::UnregisterProject>,
@@ -4761,13 +4562,13 @@ impl Project {
         Ok(())
     }
 
-    async fn handle_project_unshared(
+    async fn handle_unshare_project(
         this: ModelHandle<Self>,
-        _: TypedEnvelope<proto::ProjectUnshared>,
+        _: TypedEnvelope<proto::UnshareProject>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
     ) -> Result<()> {
-        this.update(&mut cx, |this, cx| this.unshared(cx));
+        this.update(&mut cx, |this, cx| this.disconnected_from_host(cx));
         Ok(())
     }
 
@@ -4819,27 +4620,6 @@ impl Project {
         })
     }
 
-    async fn handle_join_project_request_cancelled(
-        this: ModelHandle<Self>,
-        envelope: TypedEnvelope<proto::JoinProjectRequestCancelled>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let user = this
-            .update(&mut cx, |this, cx| {
-                this.user_store.update(cx, |user_store, cx| {
-                    user_store.get_user(envelope.payload.requester_id, cx)
-                })
-            })
-            .await?;
-
-        this.update(&mut cx, |_, cx| {
-            cx.emit(Event::ContactCancelledJoinRequest(user));
-        });
-
-        Ok(())
-    }
-
     async fn handle_update_project(
         this: ModelHandle<Self>,
         envelope: TypedEnvelope<proto::UpdateProject>,
@@ -4871,7 +4651,7 @@ impl Project {
                 }
             }
 
-            this.metadata_changed(true, cx);
+            this.metadata_changed(cx);
             for (id, _) in old_worktrees_by_id {
                 cx.emit(Event::WorktreeRemoved(id));
             }
@@ -6077,9 +5857,8 @@ impl Project {
 }
 
 impl ProjectStore {
-    pub fn new(db: Arc<Db>) -> Self {
+    pub fn new() -> Self {
         Self {
-            db,
             projects: Default::default(),
         }
     }

crates/rpc/proto/zed.proto 🔗

@@ -25,15 +25,12 @@ message Envelope {
         RegisterProject register_project = 15;
         RegisterProjectResponse register_project_response = 16;
         UnregisterProject unregister_project = 17;
-        RequestJoinProject request_join_project = 18;
-        RespondToJoinProjectRequest respond_to_join_project_request = 19;
-        JoinProjectRequestCancelled join_project_request_cancelled = 20;
         JoinProject join_project = 21;
         JoinProjectResponse join_project_response = 22;
         LeaveProject leave_project = 23;
         AddProjectCollaborator add_project_collaborator = 24;
         RemoveProjectCollaborator remove_project_collaborator = 25;
-        ProjectUnshared project_unshared = 26;
+        UnshareProject unshare_project = 26;
 
         GetDefinition get_definition = 27;
         GetDefinitionResponse get_definition_response = 28;
@@ -198,9 +195,7 @@ message RoomUpdated {
     Room room = 1;
 }
 
-message RegisterProject {
-    bool online = 1;
-}
+message RegisterProject {}
 
 message RegisterProjectResponse {
     uint64 project_id = 1;
@@ -213,55 +208,21 @@ message UnregisterProject {
 message UpdateProject {
     uint64 project_id = 1;
     repeated WorktreeMetadata worktrees = 2;
-    bool online = 3;
 }
 
 message RegisterProjectActivity {
     uint64 project_id = 1;
 }
 
-message RequestJoinProject {
-    uint64 requester_id = 1;
-    uint64 project_id = 2;
-}
-
-message RespondToJoinProjectRequest {
-    uint64 requester_id = 1;
-    uint64 project_id = 2;
-    bool allow = 3;
-}
-
-message JoinProjectRequestCancelled {
-    uint64 requester_id = 1;
-    uint64 project_id = 2;
-}
-
 message JoinProject {
     uint64 project_id = 1;
 }
 
 message JoinProjectResponse {
-    oneof variant {
-        Accept accept = 1;
-        Decline decline = 2;
-    }
-
-    message Accept {
-        uint32 replica_id = 1;
-        repeated WorktreeMetadata worktrees = 2;
-        repeated Collaborator collaborators = 3;
-        repeated LanguageServer language_servers = 4;        
-    }
-    
-    message Decline {
-        Reason reason = 1;
-
-        enum Reason {
-            Declined = 0;
-            Closed = 1;
-            WentOffline = 2;
-        }
-    }
+    uint32 replica_id = 1;
+    repeated WorktreeMetadata worktrees = 2;
+    repeated Collaborator collaborators = 3;
+    repeated LanguageServer language_servers = 4;
 }
 
 message LeaveProject {
@@ -324,7 +285,7 @@ message RemoveProjectCollaborator {
     uint32 peer_id = 2;
 }
 
-message ProjectUnshared {
+message UnshareProject {
     uint64 project_id = 1;
 }
 

crates/rpc/src/proto.rs 🔗

@@ -126,7 +126,6 @@ messages!(
     (JoinChannelResponse, Foreground),
     (JoinProject, Foreground),
     (JoinProjectResponse, Foreground),
-    (JoinProjectRequestCancelled, Foreground),
     (JoinRoom, Foreground),
     (JoinRoomResponse, Foreground),
     (LeaveChannel, Foreground),
@@ -142,7 +141,6 @@ messages!(
     (PrepareRename, Background),
     (PrepareRenameResponse, Background),
     (ProjectEntryResponse, Foreground),
-    (ProjectUnshared, Foreground),
     (RegisterProjectResponse, Foreground),
     (RemoveContact, Foreground),
     (Ping, Foreground),
@@ -153,9 +151,7 @@ messages!(
     (RemoveProjectCollaborator, Foreground),
     (RenameProjectEntry, Foreground),
     (RequestContact, Foreground),
-    (RequestJoinProject, Foreground),
     (RespondToContactRequest, Foreground),
-    (RespondToJoinProjectRequest, Foreground),
     (RoomUpdated, Foreground),
     (SaveBuffer, Foreground),
     (SearchProject, Background),
@@ -167,6 +163,7 @@ messages!(
     (Test, Foreground),
     (Unfollow, Foreground),
     (UnregisterProject, Foreground),
+    (UnshareProject, Foreground),
     (UpdateBuffer, Foreground),
     (UpdateBufferFile, Foreground),
     (UpdateContacts, Foreground),
@@ -252,24 +249,22 @@ entity_messages!(
     GetReferences,
     GetProjectSymbols,
     JoinProject,
-    JoinProjectRequestCancelled,
     LeaveProject,
     OpenBufferById,
     OpenBufferByPath,
     OpenBufferForSymbol,
     PerformRename,
     PrepareRename,
-    ProjectUnshared,
     RegisterProjectActivity,
     ReloadBuffers,
     RemoveProjectCollaborator,
     RenameProjectEntry,
-    RequestJoinProject,
     SaveBuffer,
     SearchProject,
     StartLanguageServer,
     Unfollow,
     UnregisterProject,
+    UnshareProject,
     UpdateBuffer,
     UpdateBufferFile,
     UpdateDiagnosticSummary,

crates/workspace/src/workspace.rs 🔗

@@ -107,12 +107,6 @@ pub struct OpenPaths {
     pub paths: Vec<PathBuf>,
 }
 
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct ToggleProjectOnline {
-    #[serde(skip_deserializing)]
-    pub project: Option<ModelHandle<Project>>,
-}
-
 #[derive(Clone, Deserialize, PartialEq)]
 pub struct ActivatePane(pub usize);
 
@@ -134,7 +128,7 @@ impl_internal_actions!(
         RemoveWorktreeFromProject
     ]
 );
-impl_actions!(workspace, [ToggleProjectOnline, ActivatePane]);
+impl_actions!(workspace, [ActivatePane]);
 
 pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
     pane::init(cx);
@@ -172,7 +166,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
     cx.add_async_action(Workspace::save_all);
     cx.add_action(Workspace::add_folder_to_project);
     cx.add_action(Workspace::remove_folder_from_project);
-    cx.add_action(Workspace::toggle_project_online);
     cx.add_action(
         |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
             let pane = workspace.active_pane().clone();
@@ -840,7 +833,7 @@ impl AppState {
         let languages = Arc::new(LanguageRegistry::test());
         let http_client = client::test::FakeHttpClient::with_404_response();
         let client = Client::new(http_client.clone());
-        let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
+        let project_store = cx.add_model(|_| ProjectStore::new());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
         let themes = ThemeRegistry::new((), cx.font_cache().clone());
         Arc::new(Self {
@@ -1086,7 +1079,6 @@ impl Workspace {
             let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
                 let mut workspace = Workspace::new(
                     Project::local(
-                        false,
                         app_state.client.clone(),
                         app_state.user_store.clone(),
                         app_state.project_store.clone(),
@@ -1291,17 +1283,6 @@ impl Workspace {
             .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
     }
 
-    fn toggle_project_online(&mut self, action: &ToggleProjectOnline, cx: &mut ViewContext<Self>) {
-        let project = action
-            .project
-            .clone()
-            .unwrap_or_else(|| self.project.clone());
-        project.update(cx, |project, cx| {
-            let public = !project.is_online();
-            project.set_online(public, cx);
-        });
-    }
-
     fn project_path_for_path(
         &self,
         abs_path: &Path,
@@ -2617,7 +2598,6 @@ pub fn open_paths(
 
             cx.add_window((app_state.build_window_options)(), |cx| {
                 let project = Project::local(
-                    false,
                     app_state.client.clone(),
                     app_state.user_store.clone(),
                     app_state.project_store.clone(),
@@ -2642,13 +2622,6 @@ pub fn open_paths(
             })
             .await;
 
-        if let Some(project) = new_project {
-            project
-                .update(&mut cx, |project, cx| project.restore_state(cx))
-                .await
-                .log_err();
-        }
-
         (workspace, items)
     })
 }
@@ -2657,7 +2630,6 @@ fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
     let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
         let mut workspace = Workspace::new(
             Project::local(
-                false,
                 app_state.client.clone(),
                 app_state.user_store.clone(),
                 app_state.project_store.clone(),

crates/zed/src/main.rs 🔗

@@ -140,7 +140,7 @@ fn main() {
         })
         .detach();
 
-        let project_store = cx.add_model(|_| ProjectStore::new(db.clone()));
+        let project_store = cx.add_model(|_| ProjectStore::new());
         let app_state = Arc::new(AppState {
             languages,
             themes,

crates/zed/src/zed.rs 🔗

@@ -286,12 +286,7 @@ pub fn initialize_workspace(
 
     let project_panel = ProjectPanel::new(workspace.project().clone(), cx);
     let contact_panel = cx.add_view(|cx| {
-        ContactsPanel::new(
-            app_state.user_store.clone(),
-            app_state.project_store.clone(),
-            workspace.weak_handle(),
-            cx,
-        )
+        ContactsPanel::new(app_state.user_store.clone(), workspace.weak_handle(), cx)
     });
 
     workspace.left_sidebar().update(cx, |sidebar, cx| {