diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 0fcf5d7698..01adf9e39d 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -1,5 +1,5 @@ mod participant; -mod room; +pub mod room; use anyhow::{anyhow, Result}; use client::{incoming_call::IncomingCall, Client, UserStore}; diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index d6a0012e30..0e9ce95c21 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -1,14 +1,15 @@ use crate::participant::{ParticipantLocation, RemoteParticipant}; use anyhow::{anyhow, Result}; use client::{incoming_call::IncomingCall, proto, Client, PeerId, TypedEnvelope, User, UserStore}; -use collections::HashMap; +use collections::{HashMap, HashSet}; use futures::StreamExt; use gpui::{AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task}; use std::sync::Arc; use util::ResultExt; +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { - PeerChangedActiveProject, + RemoteProjectShared { owner: Arc, project_id: u64 }, } pub struct Room { @@ -158,19 +159,46 @@ impl Room { this.update(&mut cx, |this, cx| { if let Some(participants) = participants.log_err() { - // TODO: compute diff instead of clearing participants - this.remote_participants.clear(); + let mut seen_participants = HashSet::default(); + for (participant, user) in room.participants.into_iter().zip(participants) { + let peer_id = PeerId(participant.peer_id); + seen_participants.insert(peer_id); + + let existing_project_ids = this + .remote_participants + .get(&peer_id) + .map(|existing| existing.project_ids.clone()) + .unwrap_or_default(); + for project_id in &participant.project_ids { + if !existing_project_ids.contains(project_id) { + cx.emit(Event::RemoteProjectShared { + owner: user.clone(), + project_id: *project_id, + }); + } + } + this.remote_participants.insert( - PeerId(participant.peer_id), + peer_id, RemoteParticipant { - user, + user: user.clone(), project_ids: participant.project_ids, location: ParticipantLocation::from_proto(participant.location) .unwrap_or(ParticipantLocation::External), }, ); } + + for participant_peer_id in + this.remote_participants.keys().copied().collect::>() + { + if !seen_participants.contains(&participant_peer_id) { + this.remote_participants.remove(&participant_peer_id); + } + } + + cx.notify(); } if let Some(pending_users) = pending_users.log_err() { diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 9f86020044..0529045c77 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -9,7 +9,7 @@ use rpc::proto::{RequestMessage, UsersResponse}; use std::sync::{Arc, Weak}; use util::TryFutureExt as _; -#[derive(Debug)] +#[derive(Default, Debug)] pub struct User { pub id: u64, pub github_login: String, diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 0bc848f107..5754513e1e 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -5,10 +5,10 @@ use crate::{ }; use ::rpc::Peer; use anyhow::anyhow; -use call::Room; +use call::{room, Room}; use client::{ self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection, - Credentials, EstablishConnectionError, UserStore, RECEIVE_TIMEOUT, + Credentials, EstablishConnectionError, User, UserStore, RECEIVE_TIMEOUT, }; use collections::{BTreeMap, HashMap, HashSet}; use editor::{ @@ -40,7 +40,8 @@ use serde_json::json; use settings::{Formatter, Settings}; use sqlx::types::time::OffsetDateTime; use std::{ - env, + cell::RefCell, + env, mem, ops::Deref, path::{Path, PathBuf}, rc::Rc, @@ -556,6 +557,86 @@ async fn test_host_disconnect( }); } +#[gpui::test(iterations = 10)] +async fn test_room_events( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.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; + client_a.fs.insert_tree("/a", json!({})).await; + client_b.fs.insert_tree("/b", json!({})).await; + + let (project_a, _) = client_a.build_local_project("/a", cx_a).await; + let (project_b, _) = client_b.build_local_project("/b", cx_b).await; + + let (room_id, mut rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + + let room_a = rooms.remove(0); + let room_a_events = room_events(&room_a, cx_a); + + let room_b = rooms.remove(0); + let room_b_events = room_events(&room_b, cx_b); + + let project_a_id = project_a + .update(cx_a, |project, cx| project.share(room_id, cx)) + .await + .unwrap(); + deterministic.run_until_parked(); + assert_eq!(mem::take(&mut *room_a_events.borrow_mut()), vec![]); + assert_eq!( + mem::take(&mut *room_b_events.borrow_mut()), + vec![room::Event::RemoteProjectShared { + owner: Arc::new(User { + id: client_a.user_id().unwrap(), + github_login: "user_a".to_string(), + avatar: None, + }), + project_id: project_a_id, + }] + ); + + let project_b_id = project_b + .update(cx_b, |project, cx| project.share(room_id, cx)) + .await + .unwrap(); + deterministic.run_until_parked(); + assert_eq!( + mem::take(&mut *room_a_events.borrow_mut()), + vec![room::Event::RemoteProjectShared { + owner: Arc::new(User { + id: client_b.user_id().unwrap(), + github_login: "user_b".to_string(), + avatar: None, + }), + project_id: project_b_id, + }] + ); + assert_eq!(mem::take(&mut *room_b_events.borrow_mut()), vec![]); + + fn room_events( + room: &ModelHandle, + cx: &mut TestAppContext, + ) -> Rc>> { + let events = Rc::new(RefCell::new(Vec::new())); + cx.update({ + let events = events.clone(); + |cx| { + cx.subscribe(room, move |_, event, _| { + events.borrow_mut().push(event.clone()) + }) + .detach() + } + }); + events + } +} + #[gpui::test(iterations = 10)] async fn test_propagate_saves_and_fs_changes( cx_a: &mut TestAppContext,