Cancel join requests when the requester closes the window
This commit is contained in:
parent
7c3eebf93e
commit
d821e7a4c1
10 changed files with 449 additions and 214 deletions
|
@ -650,19 +650,32 @@ impl Server {
|
|||
let project_id = request.payload.project_id;
|
||||
let project;
|
||||
{
|
||||
let mut state = self.store_mut().await;
|
||||
project = state.leave_project(sender_id, project_id)?;
|
||||
let unshare = project.connection_ids.len() <= 1;
|
||||
broadcast(sender_id, project.connection_ids, |conn_id| {
|
||||
let mut store = self.store_mut().await;
|
||||
project = store.leave_project(sender_id, project_id)?;
|
||||
|
||||
if project.remove_collaborator {
|
||||
broadcast(sender_id, project.connection_ids, |conn_id| {
|
||||
self.peer.send(
|
||||
conn_id,
|
||||
proto::RemoveProjectCollaborator {
|
||||
project_id,
|
||||
peer_id: sender_id.0,
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(requester_id) = project.cancel_request {
|
||||
self.peer.send(
|
||||
conn_id,
|
||||
proto::RemoveProjectCollaborator {
|
||||
project.host_connection_id,
|
||||
proto::JoinProjectRequestCancelled {
|
||||
project_id,
|
||||
peer_id: sender_id.0,
|
||||
requester_id: requester_id.to_proto(),
|
||||
},
|
||||
)
|
||||
});
|
||||
if unshare {
|
||||
)?;
|
||||
}
|
||||
|
||||
if project.unshare {
|
||||
self.peer.send(
|
||||
project.host_connection_id,
|
||||
proto::ProjectUnshared { project_id },
|
||||
|
@ -1633,6 +1646,7 @@ mod tests {
|
|||
use settings::Settings;
|
||||
use sqlx::types::time::OffsetDateTime;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
env,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
|
@ -2049,6 +2063,105 @@ mod tests {
|
|||
));
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_cancel_join_request(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
let lang_registry = Arc::new(LanguageRegistry::test());
|
||||
let fs = FakeFs::new(cx_a.background());
|
||||
cx_a.foreground().forbid_parking();
|
||||
|
||||
// Connect to a server as 2 clients.
|
||||
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;
|
||||
|
||||
// Share a project as client A
|
||||
fs.insert_tree("/a", json!({})).await;
|
||||
let project_a = cx_a.update(|cx| {
|
||||
Project::local(
|
||||
client_a.clone(),
|
||||
client_a.user_store.clone(),
|
||||
lang_registry.clone(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let project_id = project_a
|
||||
.read_with(cx_a, |project, _| project.next_remote_id())
|
||||
.await;
|
||||
|
||||
let project_a_events = Rc::new(RefCell::new(Vec::new()));
|
||||
let user_b = client_a
|
||||
.user_store
|
||||
.update(cx_a, |store, cx| {
|
||||
store.fetch_user(client_b.user_id().unwrap(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
let (worktree_a, _) = project_a
|
||||
.update(cx_a, |p, cx| {
|
||||
p.find_or_create_local_worktree("/a", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
worktree_a
|
||||
.read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
|
||||
// Request to join that project as client B
|
||||
let project_b = cx_b.spawn(|mut cx| {
|
||||
let client = client_b.client.clone();
|
||||
let user_store = client_b.user_store.clone();
|
||||
let lang_registry = lang_registry.clone();
|
||||
async move {
|
||||
Project::remote(
|
||||
project_id,
|
||||
client,
|
||||
user_store,
|
||||
lang_registry.clone(),
|
||||
FakeFs::new(cx.background()),
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
});
|
||||
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.clone())]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_propagate_saves_and_fs_changes(
|
||||
cx_a: &mut TestAppContext,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::db::{self, ChannelId, UserId};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet};
|
||||
use rpc::{proto, ConnectionId, Receipt};
|
||||
use std::{collections::hash_map, path::PathBuf};
|
||||
use tracing::instrument;
|
||||
|
@ -56,9 +56,12 @@ pub struct RemovedConnectionState {
|
|||
}
|
||||
|
||||
pub struct LeftProject {
|
||||
pub connection_ids: Vec<ConnectionId>,
|
||||
pub host_user_id: UserId,
|
||||
pub host_connection_id: ConnectionId,
|
||||
pub connection_ids: Vec<ConnectionId>,
|
||||
pub remove_collaborator: bool,
|
||||
pub cancel_request: Option<UserId>,
|
||||
pub unshare: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -503,24 +506,48 @@ impl Store {
|
|||
connection_id: ConnectionId,
|
||||
project_id: u64,
|
||||
) -> Result<LeftProject> {
|
||||
let user_id = self.user_id_for_connection(connection_id)?;
|
||||
let project = self
|
||||
.projects
|
||||
.get_mut(&project_id)
|
||||
.ok_or_else(|| anyhow!("no such project"))?;
|
||||
let (replica_id, _) = project
|
||||
.guests
|
||||
.remove(&connection_id)
|
||||
.ok_or_else(|| anyhow!("cannot leave a project before joining it"))?;
|
||||
project.active_replica_ids.remove(&replica_id);
|
||||
|
||||
// If the connection leaving the project is a collaborator, remove it.
|
||||
let remove_collaborator =
|
||||
if let Some((replica_id, _)) = project.guests.remove(&connection_id) {
|
||||
project.active_replica_ids.remove(&replica_id);
|
||||
true
|
||||
} else {
|
||||
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();
|
||||
|
||||
Ok(LeftProject {
|
||||
connection_ids: project.connection_ids(),
|
||||
host_connection_id: project.host_connection_id,
|
||||
host_user_id: project.host_user_id,
|
||||
connection_ids,
|
||||
cancel_request,
|
||||
unshare,
|
||||
remove_collaborator,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue