diff --git a/crates/call/src/participant.rs b/crates/call/src/participant.rs index 11a58b4b098cc6a255f8c1b061d76cf44c64684b..5f3d2827f821ec36b5cdfb63d3b8cd8247fb455e 100644 --- a/crates/call/src/participant.rs +++ b/crates/call/src/participant.rs @@ -36,6 +36,7 @@ impl ParticipantLocation { pub struct LocalParticipant { pub projects: Vec, pub active_project: Option>, + pub role: proto::ChannelRole, } #[derive(Clone, Debug)] diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 14d9a55ef03c92f13e1ecfb398963cf57db7710c..78e609c73f2f7c83ac701c1bf7795d8992c911c0 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -247,14 +247,18 @@ impl Room { let response = client.request(proto::CreateRoom {}).await?; let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?; let room = cx.new_model(|cx| { - Self::new( + let mut room = Self::new( room_proto.id, None, response.live_kit_connection_info, client, user_store, cx, - ) + ); + if let Some(participant) = room_proto.participants.first() { + room.local_participant.role = participant.role() + } + room })?; let initial_project_id = if let Some(initial_project) = initial_project { @@ -710,7 +714,21 @@ impl Room { this.participant_user_ids.clear(); if let Some(participant) = local_participant { + let role = participant.role(); this.local_participant.projects = participant.projects; + if this.local_participant.role != role { + this.local_participant.role = role; + // TODO!() this may be better done using optional replica ids instead. + // (though need to figure out how to handle promotion? join and leave the project?) + this.joined_projects.retain(|project| { + if let Some(project) = project.upgrade() { + project.update(cx, |project, _| project.set_role(role)); + true + } else { + false + } + }); + } } else { this.local_participant.projects.clear(); } @@ -1091,10 +1109,19 @@ impl Room { ) -> Task>> { let client = self.client.clone(); let user_store = self.user_store.clone(); + let role = self.local_participant.role; cx.emit(Event::RemoteProjectJoined { project_id: id }); cx.spawn(move |this, mut cx| async move { - let project = - Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?; + let project = Project::remote( + id, + client, + user_store, + language_registry, + fs, + role, + cx.clone(), + ) + .await?; this.update(&mut cx, |this, cx| { this.joined_projects.retain(|project| { diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 9a14aabfda3ba5d8449fea47397d92be5f217690..9c28e998c95426bc1026bcc5df86e8c528c4da8b 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -165,15 +165,16 @@ impl Database { if role.is_none() || role == Some(ChannelRole::Banned) { Err(anyhow!("not allowed"))? } + let role = role.unwrap(); let live_kit_room = format!("channel-{}", nanoid::nanoid!(30)); let room_id = self .get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx) .await?; - self.join_channel_room_internal(room_id, user_id, connection, &*tx) + self.join_channel_room_internal(room_id, user_id, connection, role, &*tx) .await - .map(|jr| (jr, accept_invite_result, role.unwrap())) + .map(|jr| (jr, accept_invite_result, role)) }) .await } diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 12d8940d7c3179e0f7c19881dbfea970a64b910f..ee2b0519e30f0c56f41b89dc4012ab7968babda6 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -131,7 +131,12 @@ impl Database { connection.owner_id as i32, ))), participant_index: ActiveValue::set(Some(0)), - ..Default::default() + role: ActiveValue::set(Some(ChannelRole::Admin)), + + id: ActiveValue::NotSet, + location_kind: ActiveValue::NotSet, + location_project_id: ActiveValue::NotSet, + initial_project_id: ActiveValue::NotSet, } .insert(&*tx) .await?; @@ -162,7 +167,13 @@ impl Database { calling_connection.owner_id as i32, ))), initial_project_id: ActiveValue::set(initial_project_id), - ..Default::default() + role: ActiveValue::set(Some(ChannelRole::Member)), + + id: ActiveValue::NotSet, + answering_connection_id: ActiveValue::NotSet, + answering_connection_server_id: ActiveValue::NotSet, + location_kind: ActiveValue::NotSet, + location_project_id: ActiveValue::NotSet, } .insert(&*tx) .await?; @@ -384,6 +395,7 @@ impl Database { room_id: RoomId, user_id: UserId, connection: ConnectionId, + role: ChannelRole, tx: &DatabaseTransaction, ) -> Result { let participant_index = self @@ -404,7 +416,11 @@ impl Database { connection.owner_id as i32, ))), participant_index: ActiveValue::Set(Some(participant_index)), - ..Default::default() + role: ActiveValue::set(Some(role)), + id: ActiveValue::NotSet, + location_kind: ActiveValue::NotSet, + location_project_id: ActiveValue::NotSet, + initial_project_id: ActiveValue::NotSet, }]) .on_conflict( OnConflict::columns([room_participant::Column::UserId]) @@ -413,6 +429,7 @@ impl Database { room_participant::Column::AnsweringConnectionServerId, room_participant::Column::AnsweringConnectionLost, room_participant::Column::ParticipantIndex, + room_participant::Column::Role, ]) .to_owned(), ) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index e64fdbec832a07d1dc3753c059653f6ea954fb00..457f085f8fe9a1d6de8df497fbff435277f6cfef 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -19,6 +19,7 @@ use project::{ search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath, }; use rand::prelude::*; +use rpc::proto::ChannelRole; use serde_json::json; use settings::SettingsStore; use std::{ @@ -3550,6 +3551,7 @@ async fn test_leaving_project( client_b.user_store().clone(), client_b.language_registry().clone(), FakeFs::new(cx.background_executor().clone()), + ChannelRole::Member, cx, ) }) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8b3abff0538f970c72555915d831f090c664f82a..28ce04f0fc4022e26d60829046276b31677f6836 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -262,6 +262,8 @@ enum ProjectClientState { }, Remote { sharing_has_stopped: bool, + // todo!() this should be represented differently! + is_read_only: bool, remote_id: u64, replica_id: ReplicaId, }, @@ -702,6 +704,7 @@ impl Project { user_store: Model, languages: Arc, fs: Arc, + role: proto::ChannelRole, mut cx: AsyncAppContext, ) -> Result> { client.authenticate_and_connect(true, &cx).await?; @@ -757,6 +760,7 @@ impl Project { client: client.clone(), client_state: Some(ProjectClientState::Remote { sharing_has_stopped: false, + is_read_only: false, remote_id, replica_id, }), @@ -797,6 +801,7 @@ impl Project { prettiers_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), }; + this.set_role(role); for worktree in worktrees { let _ = this.add_worktree(&worktree, cx); } @@ -1619,6 +1624,13 @@ impl Project { cx.notify(); } + pub fn set_role(&mut self, role: proto::ChannelRole) { + if let Some(ProjectClientState::Remote { is_read_only, .. }) = &mut self.client_state { + *is_read_only = + !(role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin) + } + } + fn disconnected_from_host_internal(&mut self, cx: &mut AppContext) { if let Some(ProjectClientState::Remote { sharing_has_stopped, @@ -1672,6 +1684,10 @@ impl Project { pub fn is_read_only(&self) -> bool { self.is_disconnected() + || match &self.client_state { + Some(ProjectClientState::Remote { is_read_only, .. }) => *is_read_only, + _ => false, + } } pub fn is_local(&self) -> bool {