diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index d000ebb309..ebdc952a0f 100644 --- a/crates/collab/src/integration_tests.rs +++ b/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, - 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, - 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, 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::(0..100); let files = fs.as_fake().files().await; diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 55c3414d85..192adb701c 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -88,11 +88,6 @@ impl Response { self.server.peer.respond(self.receipt, payload)?; Ok(()) } - - fn into_receipt(self) -> Receipt { - 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, ) -> 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, + message: TypedEnvelope, + ) -> 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, 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(()) - } + 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::>(); - async fn respond_to_join_project_request( - self: Arc, - request: TypedEnvelope, - ) -> Result<()> { - let host_user_id; - - { - 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"))?; - } - - 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(()); - } - - 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"))?; - - 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::>(); - - // 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 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(), - }), - }, - )?; - } - } - } - - // 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(), - }, - )), - }, - )?; - } - - 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()), - }, - )?; - } - } + // 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(), + }); + } + } + + 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(), + }), + }, + )?; + } + } + + // 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(), + })?; + + 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) { + self.peer.send(request.sender_id, update.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()), + }, + )?; } } - 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, ) -> 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(()) } diff --git a/crates/collab/src/rpc/store.rs b/crates/collab/src/rpc/store.rs index 54c3a25e27..deb2230147 100644 --- a/crates/collab/src/rpc/store.rs +++ b/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, - requested_projects: HashSet, channels: HashSet, } @@ -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, - #[serde(skip)] - pub join_requests: HashMap>>, pub active_replica_ids: HashSet, pub worktrees: BTreeMap, pub language_servers: Vec, @@ -98,13 +94,10 @@ pub struct LeftProject { pub host_connection_id: ConnectionId, pub connection_ids: Vec, pub remove_collaborator: bool, - pub cancel_request: Option, - pub unshare: bool, } pub struct UnsharedProject { - pub guests: HashMap, - pub pending_join_requests: HashMap>>, + pub guest_connection_ids: Vec, } #[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> { + ) -> 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 { + 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, - ) -> 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>> { - 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, 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 { - 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, bool)> { + ) -> Result> { 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( diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index a0259ab8c5..eb1afc3810 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/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, list_state: ListState, user_store: ModelHandle, - project_store: ModelHandle, filter_editor: ViewHandle, collapsed_sections: Vec
, selection: Option, @@ -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, - project_store: ModelHandle, workspace: WeakViewHandle, cx: &mut ViewContext, ) -> 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::().await.unwrap(); + server + .respond( + request.receipt(), + proto::RegisterProjectResponse { project_id: 200 }, + ) + .await; + let get_users_request = server.receive::().await.unwrap(); server .respond( @@ -920,14 +883,6 @@ mod tests { ) .await; - let request = server.receive::().await.unwrap(); - server - .respond( - request.receipt(), - proto::RegisterProjectResponse { project_id: 200 }, - ) - .await; - server.send(proto::UpdateContacts { incoming_requests: vec![proto::IncomingContactRequest { requester_id: 1, diff --git a/crates/contacts_panel/src/join_project_notification.rs b/crates/contacts_panel/src/join_project_notification.rs deleted file mode 100644 index d8e8e670cf..0000000000 --- a/crates/contacts_panel/src/join_project_notification.rs +++ /dev/null @@ -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, - user: Arc, -} - -impl JoinProjectNotification { - pub fn new(project: ModelHandle, user: Arc, cx: &mut ViewContext) -> 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.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.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) -> 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: &::Event) -> bool { - matches!(event, Event::Dismiss) - } -} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f6c20ff837..903e103d41 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -74,7 +74,6 @@ pub trait Item: Entity { } pub struct ProjectStore { - db: Arc, projects: Vec>, } @@ -126,7 +125,6 @@ pub struct Project { incomplete_buffers: HashMap>, buffer_snapshots: HashMap>, nonce: u128, - initialized_persistent_state: bool, _maintain_buffer_languages: Task<()>, } @@ -158,10 +156,7 @@ enum ProjectClientState { is_shared: bool, remote_id_tx: watch::Sender>, remote_id_rx: watch::Receiver>, - online_tx: watch::Sender, - online_rx: watch::Receiver, _maintain_remote_id: Task>, - _maintain_online_status: Task>, }, Remote { sharing_has_stopped: bool, @@ -196,8 +191,6 @@ pub enum Event { RemoteIdChanged(Option), DisconnectedFromHost, CollaboratorLeft(PeerId), - ContactRequestedJoin(Arc), - ContactCancelledJoinRequest(Arc), } pub enum LanguageServerState { @@ -382,17 +375,15 @@ impl FormatTrigger { impl Project { pub fn init(client: &Arc) { - 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, user_store: ModelHandle, project_store: ModelHandle, @@ -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) -> Task> { - 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::().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) -> Task> { - 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) { let settings = cx.global::(); @@ -860,24 +762,8 @@ impl Project { &self.fs } - pub fn set_online(&mut self, online: bool, _: &mut ModelContext) { - 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) -> Task> { - 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) -> Task> { - 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) { - if let ProjectClientState::Local { - remote_id_rx, - online_rx, - .. - } = &self.client_state - { + fn metadata_changed(&mut self, cx: &mut ModelContext) { + 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::>(); - 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::>(); + 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 { - 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::>() - } - pub fn worktree_for_id( &self, id: WorktreeId, @@ -1317,11 +1164,7 @@ impl Project { } } - fn share(&mut self, cx: &mut ModelContext) -> Task> { - if !self.is_online() { - return Task::ready(Err(anyhow!("can't share an offline project"))); - } - + pub fn share(&mut self, cx: &mut ModelContext) -> Task> { let project_id; if let ProjectClientState::Local { remote_id_rx, @@ -1394,10 +1237,15 @@ impl Project { }) } - fn unshared(&mut self, cx: &mut ModelContext) { - if let ProjectClientState::Local { is_shared, .. } = &mut self.client_state { + pub fn unshare(&mut self, cx: &mut ModelContext) -> 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, - ) { - 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, - message: TypedEnvelope, - _: Arc, - 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, _: TypedEnvelope, @@ -4761,13 +4562,13 @@ impl Project { Ok(()) } - async fn handle_project_unshared( + async fn handle_unshare_project( this: ModelHandle, - _: TypedEnvelope, + _: TypedEnvelope, _: Arc, 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, - envelope: TypedEnvelope, - _: Arc, - 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, envelope: TypedEnvelope, @@ -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) -> Self { + pub fn new() -> Self { Self { - db, projects: Default::default(), } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 751c41b209..2659ddb86d 100644 --- a/crates/rpc/proto/zed.proto +++ b/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; } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 814983938c..c2d2d2b321 100644 --- a/crates/rpc/src/proto.rs +++ b/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, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 90f01a3a5f..7aa93f47d9 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -107,12 +107,6 @@ pub struct OpenPaths { pub paths: Vec, } -#[derive(Clone, Deserialize, PartialEq)] -pub struct ToggleProjectOnline { - #[serde(skip_deserializing)] - pub project: Option>, -} - #[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, cx: &mut MutableAppContext) { pane::init(cx); @@ -172,7 +166,6 @@ pub fn init(app_state: Arc, 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| { 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) { - 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, 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(), diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index de769a6e5e..ea42c61dfb 100644 --- a/crates/zed/src/main.rs +++ b/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, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fcb6f8f74e..d41b9284c4 100644 --- a/crates/zed/src/zed.rs +++ b/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| {