Remove project join requests

This commit is contained in:
Antonio Scandurra 2022-09-30 11:35:50 +02:00
parent b35e8f0164
commit be8990ea78
11 changed files with 284 additions and 1156 deletions

View file

@ -7,7 +7,7 @@ use ::rpc::Peer;
use anyhow::anyhow; use anyhow::anyhow;
use call::Room; use call::Room;
use client::{ use client::{
self, proto, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection, self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
Credentials, EstablishConnectionError, UserStore, RECEIVE_TIMEOUT, Credentials, EstablishConnectionError, UserStore, RECEIVE_TIMEOUT,
}; };
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
@ -40,7 +40,6 @@ use serde_json::json;
use settings::{Formatter, Settings}; use settings::{Formatter, Settings};
use sqlx::types::time::OffsetDateTime; use sqlx::types::time::OffsetDateTime;
use std::{ use std::{
cell::RefCell,
env, env,
ops::Deref, ops::Deref,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -459,12 +458,15 @@ async fn test_unshare_project(
.await .await
.unwrap(); .unwrap();
// When client B leaves the project, it gets automatically unshared. // When client A unshares the project, client B's project becomes read-only.
cx_b.update(|_| drop(project_b)); project_a
.update(cx_a, |project, cx| project.unshare(cx))
.unwrap();
deterministic.run_until_parked(); deterministic.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared())); 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; 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())); assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
project_b2 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 (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 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; 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())); 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)); editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
assert!(cx_b.is_window_edited(workspace_b.window_id())); 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. // 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)); server.disconnect_client(client_a.current_user_id(cx_a));
cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT); 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()) .condition(cx_b, |project, _| project.is_read_only())
.await; .await;
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared())); 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. // Ensure client B's edited state is reset and that the whole window is blurred.
cx_b.read(|cx| { cx_b.read(|cx| {
@ -598,139 +582,6 @@ async fn test_host_disconnect(
.unwrap(); .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)] #[gpui::test(iterations = 10)]
async fn test_propagate_saves_and_fs_changes( async fn test_propagate_saves_and_fs_changes(
cx_a: &mut TestAppContext, 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 = server.create_client(&mut host_cx, "host").await;
let host_project = host_cx.update(|cx| { let host_project = host_cx.update(|cx| {
Project::local( Project::local(
true,
host.client.clone(), host.client.clone(),
host.user_store.clone(), host.user_store.clone(),
host.project_store.clone(), host.project_store.clone(),
@ -4738,6 +4588,11 @@ async fn test_random_collaboration(
.await; .await;
host_language_registry.add(Arc::new(language)); 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(); let op_start_signal = futures::channel::mpsc::unbounded();
user_ids.push(host.current_user_id(&host_cx)); user_ids.push(host.current_user_id(&host_cx));
op_start_signals.push(op_start_signal.0); op_start_signals.push(op_start_signal.0);
@ -5097,7 +4952,7 @@ impl TestServer {
let fs = FakeFs::new(cx.background()); let fs = FakeFs::new(cx.background());
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); 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 { let app_state = Arc::new(workspace::AppState {
client: client.clone(), client: client.clone(),
user_store: user_store.clone(), user_store: user_store.clone(),
@ -5283,7 +5138,6 @@ impl TestClient {
) -> (ModelHandle<Project>, WorktreeId) { ) -> (ModelHandle<Project>, WorktreeId) {
let project = cx.update(|cx| { let project = cx.update(|cx| {
Project::local( Project::local(
true,
self.client.clone(), self.client.clone(),
self.user_store.clone(), self.user_store.clone(),
self.project_store.clone(), self.project_store.clone(),
@ -5316,7 +5170,10 @@ impl TestClient {
let host_project_id = host_project let host_project_id = host_project
.read_with(host_cx, |project, _| project.next_remote_id()) .read_with(host_cx, |project, _| project.next_remote_id())
.await; .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 languages = host_project.read_with(host_cx, |project, _| project.languages().clone());
let project_b = guest_cx.spawn(|cx| { let project_b = guest_cx.spawn(|cx| {
Project::remote( Project::remote(
@ -5329,10 +5186,7 @@ impl TestClient {
cx, 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(); let project = project_b.await.unwrap();
project project
} }
@ -5369,18 +5223,6 @@ impl TestClient {
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let fs = project.read_with(cx, |project, _| project.fs().clone()); 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() { while op_start_signal.next().await.is_some() {
let distribution = rng.lock().gen_range::<usize, _>(0..100); let distribution = rng.lock().gen_range::<usize, _>(0..100);
let files = fs.as_fake().files().await; let files = fs.as_fake().files().await;

View file

@ -88,11 +88,6 @@ impl<R: RequestMessage> Response<R> {
self.server.peer.respond(self.receipt, payload)?; self.server.peer.respond(self.receipt, payload)?;
Ok(()) Ok(())
} }
fn into_receipt(self) -> Receipt<R> {
self.responded.store(true, SeqCst);
self.receipt
}
} }
pub struct Server { pub struct Server {
@ -160,7 +155,7 @@ impl Server {
.add_request_handler(Server::unregister_project) .add_request_handler(Server::unregister_project)
.add_request_handler(Server::join_project) .add_request_handler(Server::join_project)
.add_message_handler(Server::leave_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::update_project)
.add_message_handler(Server::register_project_activity) .add_message_handler(Server::register_project_activity)
.add_request_handler(Server::update_worktree) .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 { 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 .await
.user_id_for_connection(request.sender_id)?; .user_id_for_connection(request.sender_id)?;
let project_id = self.app_state.db.register_project(user_id).await?; let project_id = self.app_state.db.register_project(user_id).await?;
self.store().await.register_project( self.store()
request.sender_id, .await
project_id, .register_project(request.sender_id, project_id)?;
request.payload.online,
)?;
response.send(proto::RegisterProjectResponse { response.send(proto::RegisterProjectResponse {
project_id: project_id.to_proto(), project_id: project_id.to_proto(),
@ -746,11 +714,10 @@ impl Server {
response: Response<proto::UnregisterProject>, response: Response<proto::UnregisterProject>,
) -> Result<()> { ) -> Result<()> {
let project_id = ProjectId::from_proto(request.payload.project_id); let project_id = ProjectId::from_proto(request.payload.project_id);
let (user_id, project) = { let project = self
let mut state = self.store().await; .store()
let project = state.unregister_project(project_id, request.sender_id)?; .await
(state.user_id_for_connection(request.sender_id)?, project) .unregister_project(project_id, request.sender_id)?;
};
self.app_state.db.unregister_project(project_id).await?; self.app_state.db.unregister_project(project_id).await?;
broadcast( broadcast(
@ -765,28 +732,23 @@ impl Server {
) )
}, },
); );
for (_, receipts) in project.join_requests { response.send(proto::Ack {})?;
for receipt in receipts {
self.peer.respond( Ok(())
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 async fn unshare_project(
// request. This way, when the project's host can keep track of the project's self: Arc<Server>,
// remote id until after they've received the `UpdateContacts` message for message: TypedEnvelope<proto::UnshareProject>,
// themself. ) -> Result<()> {
self.update_user_contacts(user_id).await?; let project_id = ProjectId::from_proto(message.payload.project_id);
response.send(proto::Ack {})?; 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(()) Ok(())
} }
@ -849,62 +811,8 @@ impl Server {
return Err(anyhow!("no such project"))?; return Err(anyhow!("no such project"))?;
} }
self.store().await.request_join_project( let mut store = self.store().await;
guest_user_id, let (project, replica_id) = store.join_project(request.sender_id, project_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 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 peer_count = project.guests.len();
let mut collaborators = Vec::with_capacity(peer_count); let mut collaborators = Vec::with_capacity(peer_count);
collaborators.push(proto::Collaborator { collaborators.push(proto::Collaborator {
@ -924,10 +832,7 @@ impl Server {
// Add all guests other than the requesting user's own connections as collaborators // Add all guests other than the requesting user's own connections as collaborators
for (guest_conn_id, guest) in &project.guests { for (guest_conn_id, guest) in &project.guests {
if receipts_with_replica_ids if request.sender_id != *guest_conn_id {
.iter()
.all(|(receipt, _)| receipt.sender_id != *guest_conn_id)
{
collaborators.push(proto::Collaborator { collaborators.push(proto::Collaborator {
peer_id: guest_conn_id.0, peer_id: guest_conn_id.0,
replica_id: guest.replica_id as u32, replica_id: guest.replica_id as u32,
@ -937,39 +842,28 @@ impl Server {
} }
for conn_id in project.connection_ids() { for conn_id in project.connection_ids() {
for (receipt, replica_id) in &receipts_with_replica_ids { if conn_id != request.sender_id {
if conn_id != receipt.sender_id {
self.peer.send( self.peer.send(
conn_id, conn_id,
proto::AddProjectCollaborator { proto::AddProjectCollaborator {
project_id: project_id.to_proto(), project_id: project_id.to_proto(),
collaborator: Some(proto::Collaborator { collaborator: Some(proto::Collaborator {
peer_id: receipt.sender_id.0, peer_id: request.sender_id.0,
replica_id: *replica_id as u32, replica_id: replica_id as u32,
user_id: guest_user_id.to_proto(), user_id: guest_user_id.to_proto(),
}), }),
}, },
)?; )?;
} }
} }
}
// First, we send the metadata associated with each worktree. // First, we send the metadata associated with each worktree.
for (receipt, replica_id) in &receipts_with_replica_ids { response.send(proto::JoinProjectResponse {
self.peer.respond(
*receipt,
proto::JoinProjectResponse {
variant: Some(proto::join_project_response::Variant::Accept(
proto::join_project_response::Accept {
worktrees: worktrees.clone(), worktrees: worktrees.clone(),
replica_id: *replica_id as u32, replica_id: replica_id as u32,
collaborators: collaborators.clone(), collaborators: collaborators.clone(),
language_servers: project.language_servers.clone(), language_servers: project.language_servers.clone(),
}, })?;
)),
},
)?;
}
for (worktree_id, worktree) in &project.worktrees { for (worktree_id, worktree) in &project.worktrees {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@ -988,16 +882,13 @@ impl Server {
is_last_update: worktree.is_complete, is_last_update: worktree.is_complete,
}; };
for update in proto::split_worktree_update(message, MAX_CHUNK_SIZE) { for update in proto::split_worktree_update(message, MAX_CHUNK_SIZE) {
for (receipt, _) in &receipts_with_replica_ids { self.peer.send(request.sender_id, update.clone())?;
self.peer.send(receipt.sender_id, update.clone())?;
}
} }
// Stream this worktree's diagnostics. // Stream this worktree's diagnostics.
for summary in worktree.diagnostic_summaries.values() { for summary in worktree.diagnostic_summaries.values() {
for (receipt, _) in &receipts_with_replica_ids {
self.peer.send( self.peer.send(
receipt.sender_id, request.sender_id,
proto::UpdateDiagnosticSummary { proto::UpdateDiagnosticSummary {
project_id: project_id.to_proto(), project_id: project_id.to_proto(),
worktree_id: *worktree_id, worktree_id: *worktree_id,
@ -1006,10 +897,7 @@ impl Server {
)?; )?;
} }
} }
}
}
self.update_user_contacts(host_user_id).await?;
Ok(()) 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(()) Ok(())
} }
@ -1070,61 +939,18 @@ impl Server {
request: TypedEnvelope<proto::UpdateProject>, request: TypedEnvelope<proto::UpdateProject>,
) -> Result<()> { ) -> Result<()> {
let project_id = ProjectId::from_proto(request.payload.project_id); let project_id = ProjectId::from_proto(request.payload.project_id);
let user_id;
{ {
let mut state = self.store().await; let mut state = self.store().await;
user_id = state.user_id_for_connection(request.sender_id)?;
let guest_connection_ids = state let guest_connection_ids = state
.read_project(project_id, request.sender_id)? .read_project(project_id, request.sender_id)?
.guest_connection_ids(); .guest_connection_ids();
let unshared_project = state.update_project( state.update_project(project_id, &request.payload.worktrees, request.sender_id)?;
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| { broadcast(request.sender_id, guest_connection_ids, |connection_id| {
self.peer.forward_send( self.peer
request.sender_id, .forward_send(request.sender_id, connection_id, request.payload.clone())
connection_id,
request.payload.clone(),
)
}); });
}
}; };
self.update_user_contacts(user_id).await?;
Ok(()) Ok(())
} }
@ -1146,9 +972,7 @@ impl Server {
) -> Result<()> { ) -> Result<()> {
let project_id = ProjectId::from_proto(request.payload.project_id); let project_id = ProjectId::from_proto(request.payload.project_id);
let worktree_id = request.payload.worktree_id; let worktree_id = request.payload.worktree_id;
let (connection_ids, metadata_changed) = { let connection_ids = self.store().await.update_worktree(
let mut store = self.store().await;
let (connection_ids, metadata_changed) = store.update_worktree(
request.sender_id, request.sender_id,
project_id, project_id,
worktree_id, worktree_id,
@ -1158,20 +982,11 @@ impl Server {
request.payload.scan_id, request.payload.scan_id,
request.payload.is_last_update, request.payload.is_last_update,
)?; )?;
(connection_ids, metadata_changed)
};
broadcast(request.sender_id, connection_ids, |connection_id| { broadcast(request.sender_id, connection_ids, |connection_id| {
self.peer self.peer
.forward_send(request.sender_id, connection_id, request.payload.clone()) .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 {})?; response.send(proto::Ack {})?;
Ok(()) Ok(())
} }

View file

@ -1,7 +1,7 @@
use crate::db::{self, ChannelId, ProjectId, UserId}; use crate::db::{self, ChannelId, ProjectId, UserId};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use collections::{btree_map, hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet}; use collections::{btree_map, BTreeMap, BTreeSet, HashMap, HashSet};
use rpc::{proto, ConnectionId, Receipt}; use rpc::{proto, ConnectionId};
use serde::Serialize; use serde::Serialize;
use std::{mem, path::PathBuf, str, time::Duration}; use std::{mem, path::PathBuf, str, time::Duration};
use time::OffsetDateTime; use time::OffsetDateTime;
@ -32,7 +32,6 @@ struct ConnectionState {
user_id: UserId, user_id: UserId,
admin: bool, admin: bool,
projects: BTreeSet<ProjectId>, projects: BTreeSet<ProjectId>,
requested_projects: HashSet<ProjectId>,
channels: HashSet<ChannelId>, channels: HashSet<ChannelId>,
} }
@ -45,12 +44,9 @@ pub struct Call {
#[derive(Serialize)] #[derive(Serialize)]
pub struct Project { pub struct Project {
pub online: bool,
pub host_connection_id: ConnectionId, pub host_connection_id: ConnectionId,
pub host: Collaborator, pub host: Collaborator,
pub guests: HashMap<ConnectionId, Collaborator>, pub guests: HashMap<ConnectionId, Collaborator>,
#[serde(skip)]
pub join_requests: HashMap<UserId, Vec<Receipt<proto::JoinProject>>>,
pub active_replica_ids: HashSet<ReplicaId>, pub active_replica_ids: HashSet<ReplicaId>,
pub worktrees: BTreeMap<u64, Worktree>, pub worktrees: BTreeMap<u64, Worktree>,
pub language_servers: Vec<proto::LanguageServer>, pub language_servers: Vec<proto::LanguageServer>,
@ -98,13 +94,10 @@ pub struct LeftProject {
pub host_connection_id: ConnectionId, pub host_connection_id: ConnectionId,
pub connection_ids: Vec<ConnectionId>, pub connection_ids: Vec<ConnectionId>,
pub remove_collaborator: bool, pub remove_collaborator: bool,
pub cancel_request: Option<UserId>,
pub unshare: bool,
} }
pub struct UnsharedProject { pub struct UnsharedProject {
pub guests: HashMap<ConnectionId, Collaborator>, pub guest_connection_ids: Vec<ConnectionId>,
pub pending_join_requests: HashMap<UserId, Vec<Receipt<proto::JoinProject>>>,
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -159,7 +152,6 @@ impl Store {
user_id, user_id,
admin, admin,
projects: Default::default(), projects: Default::default(),
requested_projects: Default::default(),
channels: Default::default(), channels: Default::default(),
}, },
); );
@ -578,7 +570,6 @@ impl Store {
&mut self, &mut self,
host_connection_id: ConnectionId, host_connection_id: ConnectionId,
project_id: ProjectId, project_id: ProjectId,
online: bool,
) -> Result<()> { ) -> Result<()> {
let connection = self let connection = self
.connections .connections
@ -588,7 +579,6 @@ impl Store {
self.projects.insert( self.projects.insert(
project_id, project_id,
Project { Project {
online,
host_connection_id, host_connection_id,
host: Collaborator { host: Collaborator {
user_id: connection.user_id, user_id: connection.user_id,
@ -597,7 +587,6 @@ impl Store {
admin: connection.admin, admin: connection.admin,
}, },
guests: Default::default(), guests: Default::default(),
join_requests: Default::default(),
active_replica_ids: Default::default(), active_replica_ids: Default::default(),
worktrees: Default::default(), worktrees: Default::default(),
language_servers: Default::default(), language_servers: Default::default(),
@ -610,9 +599,8 @@ impl Store {
&mut self, &mut self,
project_id: ProjectId, project_id: ProjectId,
worktrees: &[proto::WorktreeMetadata], worktrees: &[proto::WorktreeMetadata],
online: bool,
connection_id: ConnectionId, connection_id: ConnectionId,
) -> Result<Option<UnsharedProject>> { ) -> Result<()> {
let project = self let project = self
.projects .projects
.get_mut(&project_id) .get_mut(&project_id)
@ -634,32 +622,7 @@ impl Store {
} }
} }
if online != project.online { Ok(())
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)
}
} else { } else {
Err(anyhow!("no such project"))? 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) Ok(project)
} else { } else {
Err(anyhow!("no such project"))? 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( pub fn update_diagnostic_summary(
&mut self, &mut self,
project_id: ProjectId, project_id: ProjectId,
@ -753,91 +731,37 @@ impl Store {
Err(anyhow!("no such project"))? Err(anyhow!("no such project"))?
} }
pub fn request_join_project( pub fn join_project(
&mut self, &mut self,
requester_id: UserId, requester_connection_id: ConnectionId,
project_id: ProjectId, project_id: ProjectId,
receipt: Receipt<proto::JoinProject>, ) -> Result<(&Project, ReplicaId)> {
) -> Result<()> {
let connection = self let connection = self
.connections .connections
.get_mut(&receipt.sender_id) .get_mut(&requester_connection_id)
.ok_or_else(|| anyhow!("no such connection"))?; .ok_or_else(|| anyhow!("no such connection"))?;
let project = self let project = self
.projects .projects
.get_mut(&project_id) .get_mut(&project_id)
.ok_or_else(|| anyhow!("no such project"))?; .ok_or_else(|| anyhow!("no such project"))?;
if project.online { connection.projects.insert(project_id);
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; let mut replica_id = 1;
while project.active_replica_ids.contains(&replica_id) { while project.active_replica_ids.contains(&replica_id) {
replica_id += 1; replica_id += 1;
} }
project.active_replica_ids.insert(replica_id); project.active_replica_ids.insert(replica_id);
project.guests.insert( project.guests.insert(
receipt.sender_id, requester_connection_id,
Collaborator { Collaborator {
replica_id, replica_id,
user_id: requester_id, user_id: connection.user_id,
last_activity: Some(OffsetDateTime::now_utc()), last_activity: Some(OffsetDateTime::now_utc()),
admin: requester_connection.admin, admin: connection.admin,
}, },
); );
receipts_with_replica_ids.push((receipt, replica_id));
}
project.host.last_activity = Some(OffsetDateTime::now_utc()); project.host.last_activity = Some(OffsetDateTime::now_utc());
Some((receipts_with_replica_ids, project)) Ok((project, replica_id))
} }
pub fn leave_project( pub fn leave_project(
@ -845,7 +769,6 @@ impl Store {
connection_id: ConnectionId, connection_id: ConnectionId,
project_id: ProjectId, project_id: ProjectId,
) -> Result<LeftProject> { ) -> Result<LeftProject> {
let user_id = self.user_id_for_connection(connection_id)?;
let project = self let project = self
.projects .projects
.get_mut(&project_id) .get_mut(&project_id)
@ -859,39 +782,14 @@ impl Store {
false 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) { if let Some(connection) = self.connections.get_mut(&connection_id) {
connection.projects.remove(&project_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 { Ok(LeftProject {
host_connection_id: project.host_connection_id, host_connection_id: project.host_connection_id,
host_user_id: project.host.user_id, host_user_id: project.host.user_id,
connection_ids, connection_ids: project.connection_ids(),
cancel_request,
unshare,
remove_collaborator, remove_collaborator,
}) })
} }
@ -907,15 +805,11 @@ impl Store {
updated_entries: &[proto::Entry], updated_entries: &[proto::Entry],
scan_id: u64, scan_id: u64,
is_last_update: bool, is_last_update: bool,
) -> Result<(Vec<ConnectionId>, bool)> { ) -> Result<Vec<ConnectionId>> {
let project = self.write_project(project_id, connection_id)?; 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 connection_ids = project.connection_ids();
let mut worktree = project.worktrees.entry(worktree_id).or_default(); 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(); worktree.root_name = worktree_root_name.to_string();
for entry_id in removed_entries { for entry_id in removed_entries {
@ -928,7 +822,7 @@ impl Store {
worktree.scan_id = scan_id; worktree.scan_id = scan_id;
worktree.is_complete = is_last_update; worktree.is_complete = is_last_update;
Ok((connection_ids, metadata_changed)) Ok(connection_ids)
} }
pub fn project_connection_ids( pub fn project_connection_ids(

View file

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

View file

@ -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)
}
}

View file

@ -74,7 +74,6 @@ pub trait Item: Entity {
} }
pub struct ProjectStore { pub struct ProjectStore {
db: Arc<Db>,
projects: Vec<WeakModelHandle<Project>>, projects: Vec<WeakModelHandle<Project>>,
} }
@ -126,7 +125,6 @@ pub struct Project {
incomplete_buffers: HashMap<u64, ModelHandle<Buffer>>, incomplete_buffers: HashMap<u64, ModelHandle<Buffer>>,
buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>, buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>,
nonce: u128, nonce: u128,
initialized_persistent_state: bool,
_maintain_buffer_languages: Task<()>, _maintain_buffer_languages: Task<()>,
} }
@ -158,10 +156,7 @@ enum ProjectClientState {
is_shared: bool, is_shared: bool,
remote_id_tx: watch::Sender<Option<u64>>, remote_id_tx: watch::Sender<Option<u64>>,
remote_id_rx: watch::Receiver<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_remote_id: Task<Option<()>>,
_maintain_online_status: Task<Option<()>>,
}, },
Remote { Remote {
sharing_has_stopped: bool, sharing_has_stopped: bool,
@ -196,8 +191,6 @@ pub enum Event {
RemoteIdChanged(Option<u64>), RemoteIdChanged(Option<u64>),
DisconnectedFromHost, DisconnectedFromHost,
CollaboratorLeft(PeerId), CollaboratorLeft(PeerId),
ContactRequestedJoin(Arc<User>),
ContactCancelledJoinRequest(Arc<User>),
} }
pub enum LanguageServerState { pub enum LanguageServerState {
@ -382,17 +375,15 @@ impl FormatTrigger {
impl Project { impl Project {
pub fn init(client: &Arc<Client>) { 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_add_collaborator);
client.add_model_message_handler(Self::handle_buffer_reloaded); 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_buffer_saved);
client.add_model_message_handler(Self::handle_start_language_server); 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_update_language_server);
client.add_model_message_handler(Self::handle_remove_collaborator); 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_update_project);
client.add_model_message_handler(Self::handle_unregister_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_create_buffer_for_peer);
client.add_model_message_handler(Self::handle_update_buffer_file); client.add_model_message_handler(Self::handle_update_buffer_file);
client.add_model_message_handler(Self::handle_update_buffer); client.add_model_message_handler(Self::handle_update_buffer);
@ -424,7 +415,6 @@ impl Project {
} }
pub fn local( pub fn local(
online: bool,
client: Arc<Client>, client: Arc<Client>,
user_store: ModelHandle<UserStore>, user_store: ModelHandle<UserStore>,
project_store: ModelHandle<ProjectStore>, 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(); let handle = cx.weak_handle();
project_store.update(cx, |store, cx| store.add_project(handle, cx)); project_store.update(cx, |store, cx| store.add_project(handle, cx));
@ -486,10 +459,7 @@ impl Project {
is_shared: false, is_shared: false,
remote_id_tx, remote_id_tx,
remote_id_rx, remote_id_rx,
online_tx,
online_rx,
_maintain_remote_id, _maintain_remote_id,
_maintain_online_status,
}, },
opened_buffer: watch::channel(), opened_buffer: watch::channel(),
client_subscriptions: Vec::new(), client_subscriptions: Vec::new(),
@ -510,7 +480,6 @@ impl Project {
language_server_settings: Default::default(), language_server_settings: Default::default(),
next_language_server_id: 0, next_language_server_id: 0,
nonce: StdRng::from_entropy().gen(), nonce: StdRng::from_entropy().gen(),
initialized_persistent_state: false,
} }
}) })
} }
@ -532,24 +501,6 @@ impl Project {
}) })
.await?; .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 replica_id = response.replica_id as ReplicaId;
let mut worktrees = Vec::new(); let mut worktrees = Vec::new();
@ -625,7 +576,6 @@ impl Project {
opened_buffers: Default::default(), opened_buffers: Default::default(),
buffer_snapshots: Default::default(), buffer_snapshots: Default::default(),
nonce: StdRng::from_entropy().gen(), nonce: StdRng::from_entropy().gen(),
initialized_persistent_state: false,
}; };
for worktree in worktrees { for worktree in worktrees {
this.add_worktree(&worktree, cx); this.add_worktree(&worktree, cx);
@ -668,10 +618,9 @@ impl Project {
let http_client = client::test::FakeHttpClient::with_404_response(); let http_client = client::test::FakeHttpClient::with_404_response();
let client = client::Client::new(http_client.clone()); let client = client::Client::new(http_client.clone());
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); 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_store = cx.add_model(|_| ProjectStore::new());
let project = cx.update(|cx| { let project =
Project::local(true, client, user_store, project_store, languages, fs, cx) cx.update(|cx| Project::local(client, user_store, project_store, languages, fs, cx));
});
for path in root_paths { for path in root_paths {
let (tree, _) = project let (tree, _) = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
@ -685,53 +634,6 @@ impl Project {
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>) { fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
@ -860,24 +762,8 @@ impl Project {
&self.fs &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<()>> { 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 ProjectClientState::Local { remote_id_rx, .. } = &mut self.client_state {
if let Some(remote_id) = *remote_id_rx.borrow() { if let Some(remote_id) = *remote_id_rx.borrow() {
let request = self.client.request(proto::UnregisterProject { let request = self.client.request(proto::UnregisterProject {
@ -905,7 +791,7 @@ impl Project {
*remote_id_tx.borrow_mut() = None; *remote_id_tx.borrow_mut() = None;
} }
this.client_subscriptions.clear(); this.client_subscriptions.clear();
this.metadata_changed(false, cx); this.metadata_changed(cx);
}); });
response.map(drop) response.map(drop)
}); });
@ -915,19 +801,12 @@ impl Project {
} }
fn register(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { fn register(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
if let ProjectClientState::Local { if let ProjectClientState::Local { remote_id_rx, .. } = &self.client_state {
remote_id_rx,
online_rx,
..
} = &self.client_state
{
if remote_id_rx.borrow().is_some() { if remote_id_rx.borrow().is_some() {
return Task::ready(Ok(())); return Task::ready(Ok(()));
} }
let response = self.client.request(proto::RegisterProject { let response = self.client.request(proto::RegisterProject {});
online: *online_rx.borrow(),
});
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let remote_id = response.await?.project_id; let remote_id = response.await?.project_id;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
@ -935,7 +814,7 @@ impl Project {
*remote_id_tx.borrow_mut() = Some(remote_id); *remote_id_tx.borrow_mut() = Some(remote_id);
} }
this.metadata_changed(false, cx); this.metadata_changed(cx);
cx.emit(Event::RemoteIdChanged(Some(remote_id))); cx.emit(Event::RemoteIdChanged(Some(remote_id)));
this.client_subscriptions this.client_subscriptions
.push(this.client.add_model_for_remote_entity(remote_id, cx)); .push(this.client.add_model_for_remote_entity(remote_id, cx));
@ -1001,37 +880,26 @@ impl Project {
} }
} }
fn metadata_changed(&mut self, persist: bool, cx: &mut ModelContext<Self>) { fn metadata_changed(&mut self, cx: &mut ModelContext<Self>) {
if let ProjectClientState::Local { if let ProjectClientState::Local { remote_id_rx, .. } = &self.client_state {
remote_id_rx,
online_rx,
..
} = &self.client_state
{
// Broadcast worktrees only if the project is online. // Broadcast worktrees only if the project is online.
let worktrees = if *online_rx.borrow() { let worktrees = self
self.worktrees .worktrees
.iter() .iter()
.filter_map(|worktree| { .filter_map(|worktree| {
worktree worktree
.upgrade(cx) .upgrade(cx)
.map(|worktree| worktree.read(cx).as_local().unwrap().metadata_proto()) .map(|worktree| worktree.read(cx).as_local().unwrap().metadata_proto())
}) })
.collect() .collect();
} else {
Default::default()
};
if let Some(project_id) = *remote_id_rx.borrow() { if let Some(project_id) = *remote_id_rx.borrow() {
let online = *online_rx.borrow();
self.client self.client
.send(proto::UpdateProject { .send(proto::UpdateProject {
project_id, project_id,
worktrees, worktrees,
online,
}) })
.log_err(); .log_err();
if online {
let worktrees = self.visible_worktrees(cx).collect::<Vec<_>>(); let worktrees = self.visible_worktrees(cx).collect::<Vec<_>>();
let scans_complete = let scans_complete =
futures::future::join_all(worktrees.iter().filter_map(|worktree| { futures::future::join_all(worktrees.iter().filter_map(|worktree| {
@ -1054,12 +922,8 @@ impl Project {
}) })
.detach(); .detach();
} }
}
self.project_store.update(cx, |_, cx| cx.notify()); self.project_store.update(cx, |_, cx| cx.notify());
if persist {
self.persist_state(cx).detach_and_log_err(cx);
}
cx.notify(); cx.notify();
} }
} }
@ -1097,23 +961,6 @@ impl Project {
.map(|tree| tree.read(cx).root_name()) .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( pub fn worktree_for_id(
&self, &self,
id: WorktreeId, id: WorktreeId,
@ -1317,11 +1164,7 @@ impl Project {
} }
} }
fn share(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub 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")));
}
let project_id; let project_id;
if let ProjectClientState::Local { if let ProjectClientState::Local {
remote_id_rx, remote_id_rx,
@ -1394,10 +1237,15 @@ impl Project {
}) })
} }
fn unshared(&mut self, cx: &mut ModelContext<Self>) { pub fn unshare(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
if let ProjectClientState::Local { is_shared, .. } = &mut self.client_state { if let ProjectClientState::Local {
is_shared,
remote_id_rx,
..
} = &mut self.client_state
{
if !*is_shared { if !*is_shared {
return; return Ok(());
} }
*is_shared = false; *is_shared = false;
@ -1422,37 +1270,13 @@ impl Project {
} }
cx.notify(); cx.notify();
} else { if let Some(project_id) = *remote_id_rx.borrow() {
log::error!("attempted to unshare a remote project"); self.client.send(proto::UnshareProject { project_id })?;
}
} }
pub fn respond_to_join_request( Ok(())
&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 { } else {
None Err(anyhow!("attempted to unshare a remote project"))
};
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);
} }
} }
@ -4527,7 +4351,7 @@ impl Project {
false false
} }
}); });
self.metadata_changed(true, cx); self.metadata_changed(cx);
cx.notify(); cx.notify();
} }
@ -4552,7 +4376,7 @@ impl Project {
.push(WorktreeHandle::Weak(worktree.downgrade())); .push(WorktreeHandle::Weak(worktree.downgrade()));
} }
self.metadata_changed(true, cx); self.metadata_changed(cx);
cx.observe_release(worktree, |this, worktree, cx| { cx.observe_release(worktree, |this, worktree, cx| {
this.remove_worktree(worktree.id(), cx); this.remove_worktree(worktree.id(), cx);
cx.notify(); cx.notify();
@ -4728,29 +4552,6 @@ impl Project {
// RPC message handlers // 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( async fn handle_unregister_project(
this: ModelHandle<Self>, this: ModelHandle<Self>,
_: TypedEnvelope<proto::UnregisterProject>, _: TypedEnvelope<proto::UnregisterProject>,
@ -4761,13 +4562,13 @@ impl Project {
Ok(()) Ok(())
} }
async fn handle_project_unshared( async fn handle_unshare_project(
this: ModelHandle<Self>, this: ModelHandle<Self>,
_: TypedEnvelope<proto::ProjectUnshared>, _: TypedEnvelope<proto::UnshareProject>,
_: Arc<Client>, _: Arc<Client>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
this.update(&mut cx, |this, cx| this.unshared(cx)); this.update(&mut cx, |this, cx| this.disconnected_from_host(cx));
Ok(()) 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( async fn handle_update_project(
this: ModelHandle<Self>, this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::UpdateProject>, 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 { for (id, _) in old_worktrees_by_id {
cx.emit(Event::WorktreeRemoved(id)); cx.emit(Event::WorktreeRemoved(id));
} }
@ -6077,9 +5857,8 @@ impl Project {
} }
impl ProjectStore { impl ProjectStore {
pub fn new(db: Arc<Db>) -> Self { pub fn new() -> Self {
Self { Self {
db,
projects: Default::default(), projects: Default::default(),
} }
} }

View file

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

View file

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

View file

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

View file

@ -140,7 +140,7 @@ fn main() {
}) })
.detach(); .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 { let app_state = Arc::new(AppState {
languages, languages,
themes, themes,

View file

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