From 92fa879b0c416e48ac25b8d978ad0824d25e544b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 31 Jul 2023 15:27:10 -0700 Subject: [PATCH] Add ability to join a room from a channel ID co-authored-by: max --- crates/call/src/call.rs | 74 ++++++++++ crates/call/src/room.rs | 9 ++ crates/client/src/channel_store.rs | 4 +- .../20221109000000_test_schema.sql | 4 +- .../20230727150500_add_channels.sql | 3 +- crates/collab/src/db.rs | 129 +++++++++++++++--- crates/collab/src/db/channel.rs | 3 +- crates/collab/src/db/room.rs | 11 +- crates/collab/src/db/tests.rs | 84 ++++++++++-- crates/collab/src/rpc.rs | 115 ++++++++++------ crates/collab/src/tests.rs | 64 ++++++++- crates/collab/src/tests/channel_tests.rs | 55 ++++++++ crates/collab/src/tests/integration_tests.rs | 26 +--- crates/rpc/proto/zed.proto | 5 + crates/rpc/src/proto.rs | 2 + crates/rpc/src/rpc.rs | 2 +- 16 files changed, 485 insertions(+), 105 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 2defd6b40f0f778157e6da24684b36d1cd565408..1e3a381b40837cc638c4679c4470e8049156e011 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -209,6 +209,80 @@ impl ActiveCall { }) } + pub fn join_channel( + &mut self, + channel_id: u64, + cx: &mut ModelContext, + ) -> Task> { + let room = if let Some(room) = self.room().cloned() { + Some(Task::ready(Ok(room)).shared()) + } else { + self.pending_room_creation.clone() + }; + + todo!() + // let invite = if let Some(room) = room { + // cx.spawn_weak(|_, mut cx| async move { + // let room = room.await.map_err(|err| anyhow!("{:?}", err))?; + + // // TODO join_channel: + // // let initial_project_id = if let Some(initial_project) = initial_project { + // // Some( + // // room.update(&mut cx, |room, cx| room.share_project(initial_project, cx)) + // // .await?, + // // ) + // // } else { + // // None + // // }; + + // // room.update(&mut cx, |room, cx| { + // // room.call(called_user_id, initial_project_id, cx) + // // }) + // // .await?; + + // anyhow::Ok(()) + // }) + // } else { + // let client = self.client.clone(); + // let user_store = self.user_store.clone(); + // let room = cx + // .spawn(|this, mut cx| async move { + // let create_room = async { + // let room = cx + // .update(|cx| { + // Room::create_from_channel(channel_id, client, user_store, cx) + // }) + // .await?; + + // this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx)) + // .await?; + + // anyhow::Ok(room) + // }; + + // let room = create_room.await; + // this.update(&mut cx, |this, _| this.pending_room_creation = None); + // room.map_err(Arc::new) + // }) + // .shared(); + // self.pending_room_creation = Some(room.clone()); + // cx.foreground().spawn(async move { + // room.await.map_err(|err| anyhow!("{:?}", err))?; + // anyhow::Ok(()) + // }) + // }; + + // cx.spawn(|this, mut cx| async move { + // let result = invite.await; + // this.update(&mut cx, |this, cx| { + // this.pending_invites.remove(&called_user_id); + // this.report_call_event("invite", cx); + // cx.notify(); + // }); + // result + // }) + } + pub fn cancel_invite( &mut self, called_user_id: u64, diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 328a94506c136dad0fbf000b00b391d6c4025b7f..e77b5437b58a4ce2b0caa0a56ee6300b40744754 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -204,6 +204,15 @@ impl Room { } } + pub(crate) fn create_from_channel( + channel_id: u64, + client: Arc, + user_store: ModelHandle, + cx: &mut AppContext, + ) -> Task>> { + todo!() + } + pub(crate) fn create( called_user_id: u64, initial_project: Option>, diff --git a/crates/client/src/channel_store.rs b/crates/client/src/channel_store.rs index a72a189415bb2982126ee0c0fc3451ab9e2022d5..e78dafe4e87616ea26c03951e913e1052e581796 100644 --- a/crates/client/src/channel_store.rs +++ b/crates/client/src/channel_store.rs @@ -10,7 +10,7 @@ pub struct ChannelStore { channel_invitations: Vec, client: Arc, user_store: ModelHandle, - rpc_subscription: Subscription, + _rpc_subscription: Subscription, } #[derive(Debug, PartialEq)] @@ -37,7 +37,7 @@ impl ChannelStore { channel_invitations: vec![], client, user_store, - rpc_subscription, + _rpc_subscription: rpc_subscription, } } diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 1ead36fde20eddbb689a32c72a77e2bf0ca7870a..6703f98df26acb9f3755c6667cef0c374a12bcfb 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -36,7 +36,8 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b"); CREATE TABLE "rooms" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, - "live_kit_room" VARCHAR NOT NULL + "live_kit_room" VARCHAR NOT NULL, + "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE ); CREATE TABLE "projects" ( @@ -188,7 +189,6 @@ CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id"); CREATE TABLE "channels" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" VARCHAR NOT NULL, - "room_id" INTEGER REFERENCES rooms (id) ON DELETE SET NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now ); diff --git a/crates/collab/migrations/20230727150500_add_channels.sql b/crates/collab/migrations/20230727150500_add_channels.sql index 05886777929101b97db522af1be82fd17fe9bad7..2d94cb6d9769f9165382ede3074bd58e90f313a6 100644 --- a/crates/collab/migrations/20230727150500_add_channels.sql +++ b/crates/collab/migrations/20230727150500_add_channels.sql @@ -7,7 +7,6 @@ DROP TABLE "channels"; CREATE TABLE "channels" ( "id" SERIAL PRIMARY KEY, "name" VARCHAR NOT NULL, - "room_id" INTEGER REFERENCES rooms (id) ON DELETE SET NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now() ); @@ -27,3 +26,5 @@ CREATE TABLE "channel_members" ( ); CREATE UNIQUE INDEX "index_channel_members_on_channel_id_and_user_id" ON "channel_members" ("channel_id", "user_id"); + +ALTER TABLE rooms ADD COLUMN "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE; diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 46fca04658c90bc225d714c9f842e798a2aed294..5f106023f15ad91ad25fd9cf200f4f9332a76ea5 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1337,32 +1337,65 @@ impl Database { &self, room_id: RoomId, user_id: UserId, + channel_id: Option, connection: ConnectionId, ) -> Result> { self.room_transaction(room_id, |tx| async move { - let result = room_participant::Entity::update_many() - .filter( - Condition::all() - .add(room_participant::Column::RoomId.eq(room_id)) - .add(room_participant::Column::UserId.eq(user_id)) - .add(room_participant::Column::AnsweringConnectionId.is_null()), - ) - .set(room_participant::ActiveModel { + if let Some(channel_id) = channel_id { + channel_member::Entity::find() + .filter( + channel_member::Column::ChannelId + .eq(channel_id) + .and(channel_member::Column::UserId.eq(user_id)) + .and(channel_member::Column::Accepted.eq(true)), + ) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such channel membership"))?; + + room_participant::ActiveModel { + room_id: ActiveValue::set(room_id), + user_id: ActiveValue::set(user_id), answering_connection_id: ActiveValue::set(Some(connection.id as i32)), answering_connection_server_id: ActiveValue::set(Some(ServerId( connection.owner_id as i32, ))), answering_connection_lost: ActiveValue::set(false), + // Redundant for the channel join use case, used for channel and call invitations + calling_user_id: ActiveValue::set(user_id), + calling_connection_id: ActiveValue::set(connection.id as i32), + calling_connection_server_id: ActiveValue::set(Some(ServerId( + connection.owner_id as i32, + ))), ..Default::default() - }) - .exec(&*tx) + } + .insert(&*tx) .await?; - if result.rows_affected == 0 { - Err(anyhow!("room does not exist or was already joined"))? } else { - let room = self.get_room(room_id, &tx).await?; - Ok(room) + let result = room_participant::Entity::update_many() + .filter( + Condition::all() + .add(room_participant::Column::RoomId.eq(room_id)) + .add(room_participant::Column::UserId.eq(user_id)) + .add(room_participant::Column::AnsweringConnectionId.is_null()), + ) + .set(room_participant::ActiveModel { + answering_connection_id: ActiveValue::set(Some(connection.id as i32)), + answering_connection_server_id: ActiveValue::set(Some(ServerId( + connection.owner_id as i32, + ))), + answering_connection_lost: ActiveValue::set(false), + ..Default::default() + }) + .exec(&*tx) + .await?; + if result.rows_affected == 0 { + Err(anyhow!("room does not exist or was already joined"))?; + } } + + let room = self.get_room(room_id, &tx).await?; + Ok(room) }) .await } @@ -3071,6 +3104,14 @@ impl Database { .insert(&*tx) .await?; + room::ActiveModel { + channel_id: ActiveValue::Set(Some(channel.id)), + live_kit_room: ActiveValue::Set(format!("channel-{}", channel.id)), + ..Default::default() + } + .insert(&*tx) + .await?; + Ok(channel.id) }) .await @@ -3163,6 +3204,7 @@ impl Database { self.transaction(|tx| async move { let tx = tx; + // Breadth first list of all edges in this user's channels let sql = r#" WITH RECURSIVE channel_tree(child_id, parent_id, depth) AS ( SELECT channel_id as child_id, CAST(NULL as INTEGER) as parent_id, 0 @@ -3173,23 +3215,52 @@ impl Database { FROM channel_parents, channel_tree WHERE channel_parents.parent_id = channel_tree.child_id ) - SELECT channel_tree.child_id as id, channels.name, channel_tree.parent_id + SELECT channel_tree.child_id, channel_tree.parent_id FROM channel_tree - JOIN channels ON channels.id = channel_tree.child_id - ORDER BY channel_tree.depth; + ORDER BY child_id, parent_id IS NOT NULL "#; + #[derive(FromQueryResult, Debug, PartialEq)] + pub struct ChannelParent { + pub child_id: ChannelId, + pub parent_id: Option, + } + let stmt = Statement::from_sql_and_values( self.pool.get_database_backend(), sql, vec![user_id.into()], ); - Ok(channel_parent::Entity::find() + let mut parents_by_child_id = HashMap::default(); + let mut parents = channel_parent::Entity::find() .from_raw_sql(stmt) - .into_model::() - .all(&*tx) - .await?) + .into_model::() + .stream(&*tx).await?; + while let Some(parent) = parents.next().await { + let parent = parent?; + parents_by_child_id.insert(parent.child_id, parent.parent_id); + } + + drop(parents); + + let mut channels = Vec::with_capacity(parents_by_child_id.len()); + let mut rows = channel::Entity::find() + .filter(channel::Column::Id.is_in(parents_by_child_id.keys().copied())) + .stream(&*tx).await?; + + while let Some(row) = rows.next().await { + let row = row?; + channels.push(Channel { + id: row.id, + name: row.name, + parent_id: parents_by_child_id.get(&row.id).copied().flatten(), + }); + } + + drop(rows); + + Ok(channels) }) .await } @@ -3210,6 +3281,22 @@ impl Database { .await } + pub async fn get_channel_room(&self, channel_id: ChannelId) -> Result { + self.transaction(|tx| async move { + let tx = tx; + let room = channel::Model { + id: channel_id, + ..Default::default() + } + .find_related(room::Entity) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("invalid channel"))?; + Ok(room.id) + }) + .await + } + async fn transaction(&self, f: F) -> Result where F: Send + Fn(TransactionHandle) -> Fut, diff --git a/crates/collab/src/db/channel.rs b/crates/collab/src/db/channel.rs index 48e5d50e3ea2d2f59a169a4500a466aa183d0d41..8834190645b1f3b5125e8d47bbcf2e3f76495ddc 100644 --- a/crates/collab/src/db/channel.rs +++ b/crates/collab/src/db/channel.rs @@ -1,4 +1,4 @@ -use super::{ChannelId, RoomId}; +use super::ChannelId; use sea_orm::entity::prelude::*; #[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)] @@ -7,7 +7,6 @@ pub struct Model { #[sea_orm(primary_key)] pub id: ChannelId, pub name: String, - pub room_id: Option, } impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/db/room.rs b/crates/collab/src/db/room.rs index c838d1273b5a41751a7dbd6226725f7067c7abff..88514ef4f180b203e17185107ec1732837d1e524 100644 --- a/crates/collab/src/db/room.rs +++ b/crates/collab/src/db/room.rs @@ -1,4 +1,4 @@ -use super::RoomId; +use super::{ChannelId, RoomId}; use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] @@ -7,6 +7,7 @@ pub struct Model { #[sea_orm(primary_key)] pub id: RoomId, pub live_kit_room: String, + pub channel_id: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -17,6 +18,12 @@ pub enum Relation { Project, #[sea_orm(has_many = "super::follower::Entity")] Follower, + #[sea_orm( + belongs_to = "super::channel::Entity", + from = "Column::ChannelId", + to = "super::channel::Column::Id" + )] + Channel, } impl Related for Entity { @@ -39,7 +46,7 @@ impl Related for Entity { impl Related for Entity { fn to() -> RelationDef { - Relation::Follower.def() + Relation::Channel.def() } } diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 03e9eb577b930f74f48f8793f76008b3aabcec16..7ef2b39640a9cc5db09510951a8ed5ef0c9399fb 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -494,9 +494,14 @@ test_both_dbs!( ) .await .unwrap(); - db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 }) - .await - .unwrap(); + db.join_room( + room_id, + user2.user_id, + None, + ConnectionId { owner_id, id: 1 }, + ) + .await + .unwrap(); assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0); db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[]) @@ -920,11 +925,6 @@ test_both_dbs!(test_channels_postgres, test_channels_sqlite, db, { name: "zed".to_string(), parent_id: None, }, - Channel { - id: rust_id, - name: "rust".to_string(), - parent_id: None, - }, Channel { id: crdb_id, name: "crdb".to_string(), @@ -940,6 +940,11 @@ test_both_dbs!(test_channels_postgres, test_channels_sqlite, db, { name: "replace".to_string(), parent_id: Some(zed_id), }, + Channel { + id: rust_id, + name: "rust".to_string(), + parent_id: None, + }, Channel { id: cargo_id, name: "cargo".to_string(), @@ -949,6 +954,69 @@ test_both_dbs!(test_channels_postgres, test_channels_sqlite, db, { ); }); +test_both_dbs!( + test_joining_channels_postgres, + test_joining_channels_sqlite, + db, + { + let owner_id = db.create_server("test").await.unwrap().0 as u32; + + let user_1 = db + .create_user( + "user1@example.com", + false, + NewUserParams { + github_login: "user1".into(), + github_user_id: 5, + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id; + let user_2 = db + .create_user( + "user2@example.com", + false, + NewUserParams { + github_login: "user2".into(), + github_user_id: 6, + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id; + + let channel_1 = db.create_root_channel("channel_1", user_1).await.unwrap(); + let room_1 = db.get_channel_room(channel_1).await.unwrap(); + + // can join a room with membership to its channel + let room = db + .join_room( + room_1, + user_1, + Some(channel_1), + ConnectionId { owner_id, id: 1 }, + ) + .await + .unwrap(); + assert_eq!(room.participants.len(), 1); + + drop(room); + // cannot join a room without membership to its channel + assert!(db + .join_room( + room_1, + user_2, + Some(channel_1), + ConnectionId { owner_id, id: 1 } + ) + .await + .is_err()); + } +); + #[gpui::test] async fn test_multiple_signup_overwrite() { let test_db = TestDb::postgres(build_background_executor()); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 3d95d484eed246ca5ea45c47be3f78db7c1c74d5..8cf0b7e48c37443f5b392ecc1ecfc980be7282d1 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -34,7 +34,10 @@ use futures::{ use lazy_static::lazy_static; use prometheus::{register_int_gauge, IntGauge}; use rpc::{ - proto::{self, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage}, + proto::{ + self, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo, + RequestMessage, + }, Connection, ConnectionId, Peer, Receipt, TypedEnvelope, }; use serde::{Serialize, Serializer}; @@ -183,7 +186,7 @@ impl Server { server .add_request_handler(ping) - .add_request_handler(create_room) + .add_request_handler(create_room_request) .add_request_handler(join_room) .add_request_handler(rejoin_room) .add_request_handler(leave_room) @@ -243,6 +246,7 @@ impl Server { .add_request_handler(invite_channel_member) .add_request_handler(remove_channel_member) .add_request_handler(respond_to_channel_invite) + .add_request_handler(join_channel) .add_request_handler(follow) .add_message_handler(unfollow) .add_message_handler(update_followers) @@ -855,48 +859,17 @@ async fn ping(_: proto::Ping, response: Response, _session: Session Ok(()) } -async fn create_room( +async fn create_room_request( _request: proto::CreateRoom, response: Response, session: Session, ) -> Result<()> { - let live_kit_room = nanoid::nanoid!(30); - let live_kit_connection_info = if let Some(live_kit) = session.live_kit_client.as_ref() { - if let Some(_) = live_kit - .create_room(live_kit_room.clone()) - .await - .trace_err() - { - if let Some(token) = live_kit - .room_token(&live_kit_room, &session.user_id.to_string()) - .trace_err() - { - Some(proto::LiveKitConnectionInfo { - server_url: live_kit.url().into(), - token, - }) - } else { - None - } - } else { - None - } - } else { - None - }; - - { - let room = session - .db() - .await - .create_room(session.user_id, session.connection_id, &live_kit_room) - .await?; + let (room, live_kit_connection_info) = create_room(&session).await?; - response.send(proto::CreateRoomResponse { - room: Some(room.clone()), - live_kit_connection_info, - })?; - } + response.send(proto::CreateRoomResponse { + room: Some(room.clone()), + live_kit_connection_info, + })?; update_user_contacts(session.user_id, &session).await?; Ok(()) @@ -912,7 +885,7 @@ async fn join_room( let room = session .db() .await - .join_room(room_id, session.user_id, session.connection_id) + .join_room(room_id, session.user_id, None, session.connection_id) .await?; room_updated(&room, &session.peer); room.clone() @@ -2182,6 +2155,32 @@ async fn respond_to_channel_invite( Ok(()) } +async fn join_channel( + request: proto::JoinChannel, + response: Response, + session: Session, +) -> Result<()> { + let db = session.db().await; + let channel_id = ChannelId::from_proto(request.channel_id); + + todo!(); + // db.check_channel_membership(session.user_id, channel_id) + // .await?; + + let (room, live_kit_connection_info) = create_room(&session).await?; + + // db.set_channel_room(channel_id, room.id).await?; + + response.send(proto::CreateRoomResponse { + room: Some(room.clone()), + live_kit_connection_info, + })?; + + update_user_contacts(session.user_id, &session).await?; + + Ok(()) +} + async fn update_diff_base(request: proto::UpdateDiffBase, session: Session) -> Result<()> { let project_id = ProjectId::from_proto(request.project_id); let project_connection_ids = session @@ -2436,6 +2435,42 @@ fn project_left(project: &db::LeftProject, session: &Session) { } } +async fn create_room(session: &Session) -> Result<(proto::Room, Option)> { + let live_kit_room = nanoid::nanoid!(30); + + let live_kit_connection_info = { + let live_kit_room = live_kit_room.clone(); + let live_kit = session.live_kit_client.as_ref(); + + util::async_iife!({ + let live_kit = live_kit?; + + live_kit + .create_room(live_kit_room.clone()) + .await + .trace_err()?; + + let token = live_kit + .room_token(&live_kit_room, &session.user_id.to_string()) + .trace_err()?; + + Some(proto::LiveKitConnectionInfo { + server_url: live_kit.url().into(), + token, + }) + }) + } + .await; + + let room = session + .db() + .await + .create_room(session.user_id, session.connection_id, &live_kit_room) + .await?; + + Ok((room, live_kit_connection_info)) +} + pub trait ResultExt { type Ok; diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index cf302d3b4d9f5f3697a1b7cc0852bd656428fecb..a000fbd92e2f02594493954a4f4e36ded6c29476 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -5,7 +5,7 @@ use crate::{ AppState, }; use anyhow::anyhow; -use call::ActiveCall; +use call::{ActiveCall, Room}; use client::{ self, proto::PeerId, ChannelStore, Client, Connection, Credentials, EstablishConnectionError, UserStore, @@ -269,6 +269,44 @@ impl TestServer { } } + async fn make_channel( + &self, + channel: &str, + admin: (&TestClient, &mut TestAppContext), + members: &mut [(&TestClient, &mut TestAppContext)], + ) -> u64 { + let (admin_client, admin_cx) = admin; + let channel_id = admin_client + .channel_store + .update(admin_cx, |channel_store, _| { + channel_store.create_channel(channel, None) + }) + .await + .unwrap(); + + for (member_client, member_cx) in members { + admin_client + .channel_store + .update(admin_cx, |channel_store, _| { + channel_store.invite_member(channel_id, member_client.user_id().unwrap(), false) + }) + .await + .unwrap(); + + admin_cx.foreground().run_until_parked(); + + member_client + .channel_store + .update(*member_cx, |channels, _| { + channels.respond_to_channel_invite(channel_id, true) + }) + .await + .unwrap(); + } + + channel_id + } + async fn create_room(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) { self.make_contacts(clients).await; @@ -516,3 +554,27 @@ impl Drop for TestClient { self.client.teardown(); } } + +#[derive(Debug, Eq, PartialEq)] +struct RoomParticipants { + remote: Vec, + pending: Vec, +} + +fn room_participants(room: &ModelHandle, cx: &mut TestAppContext) -> RoomParticipants { + room.read_with(cx, |room, _| { + let mut remote = room + .remote_participants() + .iter() + .map(|(_, participant)| participant.user.github_login.clone()) + .collect::>(); + let mut pending = room + .pending_participants() + .iter() + .map(|user| user.github_login.clone()) + .collect::>(); + remote.sort(); + pending.sort(); + RoomParticipants { remote, pending } + }) +} diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 4cc0d24d9bee9f108917b276d134f6bf51a9c423..c86238825c6d35a4519d50c92763e6b799fa1ffc 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -1,7 +1,10 @@ +use call::ActiveCall; use client::Channel; use gpui::{executor::Deterministic, TestAppContext}; use std::sync::Arc; +use crate::tests::{room_participants, RoomParticipants}; + use super::TestServer; #[gpui::test] @@ -82,6 +85,58 @@ async fn test_basic_channels( }); } +#[gpui::test] +async fn test_channel_room( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.forbid_parking(); + let mut server = TestServer::start(&deterministic).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + + let zed_id = server + .make_channel("zed", (&client_a, cx_a), &mut [(&client_b, cx_b)]) + .await; + + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + + active_call_a + .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx)) + .await + .unwrap(); + + deterministic.run_until_parked(); + + let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone()); + assert_eq!( + room_participants(&room_a, cx_a), + RoomParticipants { + remote: vec!["user_a".to_string()], + pending: vec![] + } + ); + + active_call_b + .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx)) + .await + .unwrap(); + + deterministic.run_until_parked(); + + let active_call_b = cx_b.read(ActiveCall::global); + let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone()); + assert_eq!( + room_participants(&room_b, cx_b), + RoomParticipants { + remote: vec!["user_a".to_string(), "user_b".to_string()], + pending: vec![] + } + ); +} + // TODO: // Invariants to test: // 1. Dag structure is maintained for all operations (can't make a cycle) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index ab94f16a07f011b8166f7b7ff779539b89401034..5a27787dbca7f83b39a2af50976c23c56fa4c995 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -1,6 +1,6 @@ use crate::{ rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT}, - tests::{TestClient, TestServer}, + tests::{room_participants, RoomParticipants, TestClient, TestServer}, }; use call::{room, ActiveCall, ParticipantLocation, Room}; use client::{User, RECEIVE_TIMEOUT}; @@ -8319,30 +8319,6 @@ async fn test_inlay_hint_refresh_is_forwarded( }); } -#[derive(Debug, Eq, PartialEq)] -struct RoomParticipants { - remote: Vec, - pending: Vec, -} - -fn room_participants(room: &ModelHandle, cx: &mut TestAppContext) -> RoomParticipants { - room.read_with(cx, |room, _| { - let mut remote = room - .remote_participants() - .iter() - .map(|(_, participant)| participant.user.github_login.clone()) - .collect::>(); - let mut pending = room - .pending_participants() - .iter() - .map(|user| user.github_login.clone()) - .collect::>(); - remote.sort(); - pending.sort(); - RoomParticipants { remote, pending } - }) -} - fn extract_hint_labels(editor: &Editor) -> Vec { let mut labels = Vec::new(); for (_, excerpt_hints) in &editor.inlay_hint_cache().hints { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 38ffbe6b7e94dba6e5d90deacb9c85616972dab5..8a4a72c26849f8d1b83a321787b4e97babcd7eb1 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -136,6 +136,7 @@ message Envelope { RemoveChannelMember remove_channel_member = 122; RespondToChannelInvite respond_to_channel_invite = 123; UpdateChannels update_channels = 124; + JoinChannel join_channel = 125; } } @@ -870,6 +871,10 @@ message UpdateChannels { repeated uint64 remove_channel_invitations = 4; } +message JoinChannel { + uint64 channel_id = 1; +} + message CreateChannel { string name = 1; optional uint64 parent_id = 2; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 1e9e93a2d0188b4b669514354174b0b1b4083570..c3d65343d62365b41845fc20407830e4e78acd9b 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -214,6 +214,7 @@ messages!( (RequestContact, Foreground), (RespondToContactRequest, Foreground), (RespondToChannelInvite, Foreground), + (JoinChannel, Foreground), (RoomUpdated, Foreground), (SaveBuffer, Foreground), (SearchProject, Background), @@ -294,6 +295,7 @@ request_messages!( (RemoveContact, Ack), (RespondToContactRequest, Ack), (RespondToChannelInvite, Ack), + (JoinChannel, CreateRoomResponse), (RenameProjectEntry, ProjectEntryResponse), (SaveBuffer, BufferSaved), (SearchProject, SearchProjectResponse), diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 6b430d90e46072a6f885b60a4e912978ed26c6a2..3cb8b6bffa2ca1549ca854db39e46ef8fc8634a7 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 59; +pub const PROTOCOL_VERSION: u32 = 60;