mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 10:40:54 +00:00
Introduce an epoch to ConnectionId
and PeerId
This commit is contained in:
parent
9bd400cf16
commit
05e99eb67e
24 changed files with 714 additions and 382 deletions
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
IncomingCall,
|
IncomingCall,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
|
use client::{proto, Client, TypedEnvelope, User, UserStore};
|
||||||
use collections::{BTreeMap, HashSet};
|
use collections::{BTreeMap, HashSet};
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -20,10 +20,10 @@ pub const RECONNECT_TIMEOUT: Duration = client::RECEIVE_TIMEOUT;
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ParticipantLocationChanged {
|
ParticipantLocationChanged {
|
||||||
participant_id: PeerId,
|
participant_id: proto::PeerId,
|
||||||
},
|
},
|
||||||
RemoteVideoTracksChanged {
|
RemoteVideoTracksChanged {
|
||||||
participant_id: PeerId,
|
participant_id: proto::PeerId,
|
||||||
},
|
},
|
||||||
RemoteProjectShared {
|
RemoteProjectShared {
|
||||||
owner: Arc<User>,
|
owner: Arc<User>,
|
||||||
|
@ -41,7 +41,7 @@ pub struct Room {
|
||||||
live_kit: Option<LiveKitRoom>,
|
live_kit: Option<LiveKitRoom>,
|
||||||
status: RoomStatus,
|
status: RoomStatus,
|
||||||
local_participant: LocalParticipant,
|
local_participant: LocalParticipant,
|
||||||
remote_participants: BTreeMap<PeerId, RemoteParticipant>,
|
remote_participants: BTreeMap<proto::PeerId, RemoteParticipant>,
|
||||||
pending_participants: Vec<Arc<User>>,
|
pending_participants: Vec<Arc<User>>,
|
||||||
participant_user_ids: HashSet<u64>,
|
participant_user_ids: HashSet<u64>,
|
||||||
pending_call_count: usize,
|
pending_call_count: usize,
|
||||||
|
@ -349,7 +349,7 @@ impl Room {
|
||||||
&self.local_participant
|
&self.local_participant
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remote_participants(&self) -> &BTreeMap<PeerId, RemoteParticipant> {
|
pub fn remote_participants(&self) -> &BTreeMap<proto::PeerId, RemoteParticipant> {
|
||||||
&self.remote_participants
|
&self.remote_participants
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,7 +419,7 @@ impl Room {
|
||||||
if let Some(participants) = remote_participants.log_err() {
|
if let Some(participants) = remote_participants.log_err() {
|
||||||
let mut participant_peer_ids = HashSet::default();
|
let mut participant_peer_ids = HashSet::default();
|
||||||
for (participant, user) in room.participants.into_iter().zip(participants) {
|
for (participant, user) in room.participants.into_iter().zip(participants) {
|
||||||
let peer_id = PeerId(participant.peer_id);
|
let Some(peer_id) = participant.peer_id else { continue };
|
||||||
this.participant_user_ids.insert(participant.user_id);
|
this.participant_user_ids.insert(participant.user_id);
|
||||||
participant_peer_ids.insert(peer_id);
|
participant_peer_ids.insert(peer_id);
|
||||||
|
|
||||||
|
@ -476,7 +476,7 @@ impl Room {
|
||||||
|
|
||||||
if let Some(live_kit) = this.live_kit.as_ref() {
|
if let Some(live_kit) = this.live_kit.as_ref() {
|
||||||
let tracks =
|
let tracks =
|
||||||
live_kit.room.remote_video_tracks(&peer_id.0.to_string());
|
live_kit.room.remote_video_tracks(&peer_id.to_string());
|
||||||
for track in tracks {
|
for track in tracks {
|
||||||
this.remote_video_track_updated(
|
this.remote_video_track_updated(
|
||||||
RemoteVideoTrackUpdate::Subscribed(track),
|
RemoteVideoTrackUpdate::Subscribed(track),
|
||||||
|
@ -531,7 +531,7 @@ impl Room {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match change {
|
match change {
|
||||||
RemoteVideoTrackUpdate::Subscribed(track) => {
|
RemoteVideoTrackUpdate::Subscribed(track) => {
|
||||||
let peer_id = PeerId(track.publisher_id().parse()?);
|
let peer_id = track.publisher_id().parse()?;
|
||||||
let track_id = track.sid().to_string();
|
let track_id = track.sid().to_string();
|
||||||
let participant = self
|
let participant = self
|
||||||
.remote_participants
|
.remote_participants
|
||||||
|
@ -551,7 +551,7 @@ impl Room {
|
||||||
publisher_id,
|
publisher_id,
|
||||||
track_id,
|
track_id,
|
||||||
} => {
|
} => {
|
||||||
let peer_id = PeerId(publisher_id.parse()?);
|
let peer_id = publisher_id.parse()?;
|
||||||
let participant = self
|
let participant = self
|
||||||
.remote_participants
|
.remote_participants
|
||||||
.get_mut(&peer_id)
|
.get_mut(&peer_id)
|
||||||
|
|
|
@ -23,7 +23,7 @@ use lazy_static::lazy_static;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage};
|
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
|
@ -140,7 +140,7 @@ impl EstablishConnectionError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
SignedOut,
|
SignedOut,
|
||||||
UpgradeRequired,
|
UpgradeRequired,
|
||||||
|
@ -306,7 +306,7 @@ impl Client {
|
||||||
pub fn new(http: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
|
pub fn new(http: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
id: 0,
|
id: 0,
|
||||||
peer: Peer::new(),
|
peer: Peer::new(0),
|
||||||
telemetry: Telemetry::new(http.clone(), cx),
|
telemetry: Telemetry::new(http.clone(), cx),
|
||||||
http,
|
http,
|
||||||
state: Default::default(),
|
state: Default::default(),
|
||||||
|
@ -810,7 +810,11 @@ impl Client {
|
||||||
hello_message_type_name
|
hello_message_type_name
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
Ok(PeerId(hello.payload.peer_id))
|
let peer_id = hello
|
||||||
|
.payload
|
||||||
|
.peer_id
|
||||||
|
.ok_or_else(|| anyhow!("invalid peer id"))?;
|
||||||
|
Ok(peer_id)
|
||||||
};
|
};
|
||||||
|
|
||||||
let peer_id = match peer_id.await {
|
let peer_id = match peer_id.await {
|
||||||
|
@ -822,7 +826,7 @@ impl Client {
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"set status to connected (connection id: {}, peer id: {})",
|
"set status to connected (connection id: {:?}, peer id: {:?})",
|
||||||
connection_id,
|
connection_id,
|
||||||
peer_id
|
peer_id
|
||||||
);
|
);
|
||||||
|
@ -853,7 +857,7 @@ impl Client {
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
match handle_io.await {
|
match handle_io.await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
if *this.status().borrow()
|
if this.status().borrow().clone()
|
||||||
== (Status::Connected {
|
== (Status::Connected {
|
||||||
connection_id,
|
connection_id,
|
||||||
peer_id,
|
peer_id,
|
||||||
|
@ -1194,7 +1198,7 @@ impl Client {
|
||||||
let mut state = self.state.write();
|
let mut state = self.state.write();
|
||||||
let type_name = message.payload_type_name();
|
let type_name = message.payload_type_name();
|
||||||
let payload_type_id = message.payload_type_id();
|
let payload_type_id = message.payload_type_id();
|
||||||
let sender_id = message.original_sender_id().map(|id| id.0);
|
let sender_id = message.original_sender_id();
|
||||||
|
|
||||||
let mut subscriber = None;
|
let mut subscriber = None;
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ impl FakeServer {
|
||||||
cx: &TestAppContext,
|
cx: &TestAppContext,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let server = Self {
|
let server = Self {
|
||||||
peer: Peer::new(),
|
peer: Peer::new(0),
|
||||||
state: Default::default(),
|
state: Default::default(),
|
||||||
user_id: client_user_id,
|
user_id: client_user_id,
|
||||||
executor: cx.foreground(),
|
executor: cx.foreground(),
|
||||||
|
@ -92,7 +92,7 @@ impl FakeServer {
|
||||||
peer.send(
|
peer.send(
|
||||||
connection_id,
|
connection_id,
|
||||||
proto::Hello {
|
proto::Hello {
|
||||||
peer_id: connection_id.0,
|
peer_id: Some(connection_id.into()),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -44,7 +44,7 @@ CREATE TABLE "projects" (
|
||||||
"room_id" INTEGER REFERENCES rooms (id) NOT NULL,
|
"room_id" INTEGER REFERENCES rooms (id) NOT NULL,
|
||||||
"host_user_id" INTEGER REFERENCES users (id) NOT NULL,
|
"host_user_id" INTEGER REFERENCES users (id) NOT NULL,
|
||||||
"host_connection_id" INTEGER NOT NULL,
|
"host_connection_id" INTEGER NOT NULL,
|
||||||
"host_connection_epoch" TEXT NOT NULL,
|
"host_connection_epoch" INTEGER NOT NULL,
|
||||||
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE
|
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
);
|
);
|
||||||
CREATE INDEX "index_projects_on_host_connection_epoch" ON "projects" ("host_connection_epoch");
|
CREATE INDEX "index_projects_on_host_connection_epoch" ON "projects" ("host_connection_epoch");
|
||||||
|
@ -103,7 +103,7 @@ CREATE TABLE "project_collaborators" (
|
||||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
"project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
|
"project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
|
||||||
"connection_id" INTEGER NOT NULL,
|
"connection_id" INTEGER NOT NULL,
|
||||||
"connection_epoch" TEXT NOT NULL,
|
"connection_epoch" INTEGER NOT NULL,
|
||||||
"user_id" INTEGER NOT NULL,
|
"user_id" INTEGER NOT NULL,
|
||||||
"replica_id" INTEGER NOT NULL,
|
"replica_id" INTEGER NOT NULL,
|
||||||
"is_host" BOOLEAN NOT NULL
|
"is_host" BOOLEAN NOT NULL
|
||||||
|
@ -119,14 +119,14 @@ CREATE TABLE "room_participants" (
|
||||||
"room_id" INTEGER NOT NULL REFERENCES rooms (id),
|
"room_id" INTEGER NOT NULL REFERENCES rooms (id),
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users (id),
|
"user_id" INTEGER NOT NULL REFERENCES users (id),
|
||||||
"answering_connection_id" INTEGER,
|
"answering_connection_id" INTEGER,
|
||||||
"answering_connection_epoch" TEXT,
|
"answering_connection_epoch" INTEGER,
|
||||||
"answering_connection_lost" BOOLEAN NOT NULL,
|
"answering_connection_lost" BOOLEAN NOT NULL,
|
||||||
"location_kind" INTEGER,
|
"location_kind" INTEGER,
|
||||||
"location_project_id" INTEGER,
|
"location_project_id" INTEGER,
|
||||||
"initial_project_id" INTEGER,
|
"initial_project_id" INTEGER,
|
||||||
"calling_user_id" INTEGER NOT NULL REFERENCES users (id),
|
"calling_user_id" INTEGER NOT NULL REFERENCES users (id),
|
||||||
"calling_connection_id" INTEGER NOT NULL,
|
"calling_connection_id" INTEGER NOT NULL,
|
||||||
"calling_connection_epoch" TEXT NOT NULL
|
"calling_connection_epoch" INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX "index_room_participants_on_user_id" ON "room_participants" ("user_id");
|
CREATE UNIQUE INDEX "index_room_participants_on_user_id" ON "room_participants" ("user_id");
|
||||||
CREATE INDEX "index_room_participants_on_room_id" ON "room_participants" ("room_id");
|
CREATE INDEX "index_room_participants_on_room_id" ON "room_participants" ("room_id");
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
ALTER TABLE "projects"
|
||||||
|
ALTER COLUMN "host_connection_epoch" TYPE INTEGER USING 0;
|
||||||
|
|
||||||
|
ALTER TABLE "project_collaborators"
|
||||||
|
ALTER COLUMN "connection_epoch" TYPE INTEGER USING 0;
|
||||||
|
|
||||||
|
ALTER TABLE "room_participants"
|
||||||
|
ALTER COLUMN "answering_connection_epoch" TYPE INTEGER USING 0,
|
||||||
|
ALTER COLUMN "calling_connection_epoch" TYPE INTEGER USING 0;
|
|
@ -1076,7 +1076,7 @@ impl Database {
|
||||||
pub async fn create_room(
|
pub async fn create_room(
|
||||||
&self,
|
&self,
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
live_kit_room: &str,
|
live_kit_room: &str,
|
||||||
) -> Result<RoomGuard<proto::Room>> {
|
) -> Result<RoomGuard<proto::Room>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
|
@ -1091,12 +1091,12 @@ impl Database {
|
||||||
room_participant::ActiveModel {
|
room_participant::ActiveModel {
|
||||||
room_id: ActiveValue::set(room_id),
|
room_id: ActiveValue::set(room_id),
|
||||||
user_id: ActiveValue::set(user_id),
|
user_id: ActiveValue::set(user_id),
|
||||||
answering_connection_id: ActiveValue::set(Some(connection_id.0 as i32)),
|
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
|
||||||
answering_connection_epoch: ActiveValue::set(Some(self.epoch())),
|
answering_connection_epoch: ActiveValue::set(Some(connection.epoch as i32)),
|
||||||
answering_connection_lost: ActiveValue::set(false),
|
answering_connection_lost: ActiveValue::set(false),
|
||||||
calling_user_id: ActiveValue::set(user_id),
|
calling_user_id: ActiveValue::set(user_id),
|
||||||
calling_connection_id: ActiveValue::set(connection_id.0 as i32),
|
calling_connection_id: ActiveValue::set(connection.id as i32),
|
||||||
calling_connection_epoch: ActiveValue::set(self.epoch()),
|
calling_connection_epoch: ActiveValue::set(connection.epoch as i32),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.insert(&*tx)
|
.insert(&*tx)
|
||||||
|
@ -1112,7 +1112,7 @@ impl Database {
|
||||||
&self,
|
&self,
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
calling_user_id: UserId,
|
calling_user_id: UserId,
|
||||||
calling_connection_id: ConnectionId,
|
calling_connection: ConnectionId,
|
||||||
called_user_id: UserId,
|
called_user_id: UserId,
|
||||||
initial_project_id: Option<ProjectId>,
|
initial_project_id: Option<ProjectId>,
|
||||||
) -> Result<RoomGuard<(proto::Room, proto::IncomingCall)>> {
|
) -> Result<RoomGuard<(proto::Room, proto::IncomingCall)>> {
|
||||||
|
@ -1122,8 +1122,8 @@ impl Database {
|
||||||
user_id: ActiveValue::set(called_user_id),
|
user_id: ActiveValue::set(called_user_id),
|
||||||
answering_connection_lost: ActiveValue::set(false),
|
answering_connection_lost: ActiveValue::set(false),
|
||||||
calling_user_id: ActiveValue::set(calling_user_id),
|
calling_user_id: ActiveValue::set(calling_user_id),
|
||||||
calling_connection_id: ActiveValue::set(calling_connection_id.0 as i32),
|
calling_connection_id: ActiveValue::set(calling_connection.id as i32),
|
||||||
calling_connection_epoch: ActiveValue::set(self.epoch()),
|
calling_connection_epoch: ActiveValue::set(calling_connection.epoch as i32),
|
||||||
initial_project_id: ActiveValue::set(initial_project_id),
|
initial_project_id: ActiveValue::set(initial_project_id),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
@ -1192,19 +1192,23 @@ impl Database {
|
||||||
pub async fn cancel_call(
|
pub async fn cancel_call(
|
||||||
&self,
|
&self,
|
||||||
expected_room_id: Option<RoomId>,
|
expected_room_id: Option<RoomId>,
|
||||||
calling_connection_id: ConnectionId,
|
calling_connection: ConnectionId,
|
||||||
called_user_id: UserId,
|
called_user_id: UserId,
|
||||||
) -> Result<RoomGuard<proto::Room>> {
|
) -> Result<RoomGuard<proto::Room>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let participant = room_participant::Entity::find()
|
let participant = room_participant::Entity::find()
|
||||||
.filter(
|
.filter(
|
||||||
room_participant::Column::UserId
|
Condition::all()
|
||||||
.eq(called_user_id)
|
.add(room_participant::Column::UserId.eq(called_user_id))
|
||||||
.and(
|
.add(
|
||||||
room_participant::Column::CallingConnectionId
|
room_participant::Column::CallingConnectionId
|
||||||
.eq(calling_connection_id.0 as i32),
|
.eq(calling_connection.id as i32),
|
||||||
)
|
)
|
||||||
.and(room_participant::Column::AnsweringConnectionId.is_null()),
|
.add(
|
||||||
|
room_participant::Column::CallingConnectionEpoch
|
||||||
|
.eq(calling_connection.epoch as i32),
|
||||||
|
)
|
||||||
|
.add(room_participant::Column::AnsweringConnectionId.is_null()),
|
||||||
)
|
)
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
|
@ -1228,7 +1232,7 @@ impl Database {
|
||||||
&self,
|
&self,
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<RoomGuard<proto::Room>> {
|
) -> Result<RoomGuard<proto::Room>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let result = room_participant::Entity::update_many()
|
let result = room_participant::Entity::update_many()
|
||||||
|
@ -1242,13 +1246,13 @@ impl Database {
|
||||||
.add(room_participant::Column::AnsweringConnectionLost.eq(true))
|
.add(room_participant::Column::AnsweringConnectionLost.eq(true))
|
||||||
.add(
|
.add(
|
||||||
room_participant::Column::AnsweringConnectionEpoch
|
room_participant::Column::AnsweringConnectionEpoch
|
||||||
.ne(self.epoch()),
|
.ne(connection.epoch as i32),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.set(room_participant::ActiveModel {
|
.set(room_participant::ActiveModel {
|
||||||
answering_connection_id: ActiveValue::set(Some(connection_id.0 as i32)),
|
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
|
||||||
answering_connection_epoch: ActiveValue::set(Some(self.epoch())),
|
answering_connection_epoch: ActiveValue::set(Some(connection.epoch as i32)),
|
||||||
answering_connection_lost: ActiveValue::set(false),
|
answering_connection_lost: ActiveValue::set(false),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
@ -1264,10 +1268,20 @@ impl Database {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn leave_room(&self, connection_id: ConnectionId) -> Result<RoomGuard<LeftRoom>> {
|
pub async fn leave_room(&self, connection: ConnectionId) -> Result<RoomGuard<LeftRoom>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let leaving_participant = room_participant::Entity::find()
|
let leaving_participant = room_participant::Entity::find()
|
||||||
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(
|
||||||
|
room_participant::Column::AnsweringConnectionId
|
||||||
|
.eq(connection.id as i32),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
room_participant::Column::AnsweringConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
),
|
||||||
|
)
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -1281,9 +1295,16 @@ impl Database {
|
||||||
// Cancel pending calls initiated by the leaving user.
|
// Cancel pending calls initiated by the leaving user.
|
||||||
let called_participants = room_participant::Entity::find()
|
let called_participants = room_participant::Entity::find()
|
||||||
.filter(
|
.filter(
|
||||||
room_participant::Column::CallingConnectionId
|
Condition::all()
|
||||||
.eq(connection_id.0)
|
.add(
|
||||||
.and(room_participant::Column::AnsweringConnectionId.is_null()),
|
room_participant::Column::CallingConnectionId
|
||||||
|
.eq(connection.id as i32),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
room_participant::Column::CallingConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
)
|
||||||
|
.add(room_participant::Column::AnsweringConnectionId.is_null()),
|
||||||
)
|
)
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -1310,7 +1331,16 @@ impl Database {
|
||||||
project_collaborator::Column::ProjectId,
|
project_collaborator::Column::ProjectId,
|
||||||
QueryProjectIds::ProjectId,
|
QueryProjectIds::ProjectId,
|
||||||
)
|
)
|
||||||
.filter(project_collaborator::Column::ConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(
|
||||||
|
project_collaborator::Column::ConnectionId.eq(connection.id as i32),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
project_collaborator::Column::ConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
),
|
||||||
|
)
|
||||||
.into_values::<_, QueryProjectIds>()
|
.into_values::<_, QueryProjectIds>()
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -1331,32 +1361,43 @@ impl Database {
|
||||||
host_connection_id: Default::default(),
|
host_connection_id: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let collaborator_connection_id =
|
let collaborator_connection_id = ConnectionId {
|
||||||
ConnectionId(collaborator.connection_id as u32);
|
epoch: collaborator.connection_epoch as u32,
|
||||||
if collaborator_connection_id != connection_id {
|
id: collaborator.connection_id as u32,
|
||||||
|
};
|
||||||
|
if collaborator_connection_id != connection {
|
||||||
left_project.connection_ids.push(collaborator_connection_id);
|
left_project.connection_ids.push(collaborator_connection_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if collaborator.is_host {
|
if collaborator.is_host {
|
||||||
left_project.host_user_id = collaborator.user_id;
|
left_project.host_user_id = collaborator.user_id;
|
||||||
left_project.host_connection_id =
|
left_project.host_connection_id = collaborator_connection_id;
|
||||||
ConnectionId(collaborator.connection_id as u32);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drop(collaborators);
|
drop(collaborators);
|
||||||
|
|
||||||
// Leave projects.
|
// Leave projects.
|
||||||
project_collaborator::Entity::delete_many()
|
project_collaborator::Entity::delete_many()
|
||||||
.filter(project_collaborator::Column::ConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(
|
||||||
|
project_collaborator::Column::ConnectionId.eq(connection.id as i32),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
project_collaborator::Column::ConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
),
|
||||||
|
)
|
||||||
.exec(&*tx)
|
.exec(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Unshare projects.
|
// Unshare projects.
|
||||||
project::Entity::delete_many()
|
project::Entity::delete_many()
|
||||||
.filter(
|
.filter(
|
||||||
project::Column::RoomId
|
Condition::all()
|
||||||
.eq(room_id)
|
.add(project::Column::RoomId.eq(room_id))
|
||||||
.and(project::Column::HostConnectionId.eq(connection_id.0 as i32)),
|
.add(project::Column::HostConnectionId.eq(connection.id as i32))
|
||||||
|
.add(project::Column::HostConnectionEpoch.eq(connection.epoch as i32)),
|
||||||
)
|
)
|
||||||
.exec(&*tx)
|
.exec(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -1387,7 +1428,7 @@ impl Database {
|
||||||
pub async fn update_room_participant_location(
|
pub async fn update_room_participant_location(
|
||||||
&self,
|
&self,
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
location: proto::ParticipantLocation,
|
location: proto::ParticipantLocation,
|
||||||
) -> Result<RoomGuard<proto::Room>> {
|
) -> Result<RoomGuard<proto::Room>> {
|
||||||
self.room_transaction(|tx| async {
|
self.room_transaction(|tx| async {
|
||||||
|
@ -1414,9 +1455,18 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = room_participant::Entity::update_many()
|
let result = room_participant::Entity::update_many()
|
||||||
.filter(room_participant::Column::RoomId.eq(room_id).and(
|
.filter(
|
||||||
room_participant::Column::AnsweringConnectionId.eq(connection_id.0 as i32),
|
Condition::all()
|
||||||
))
|
.add(room_participant::Column::RoomId.eq(room_id))
|
||||||
|
.add(
|
||||||
|
room_participant::Column::AnsweringConnectionId
|
||||||
|
.eq(connection.id as i32),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
room_participant::Column::AnsweringConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
),
|
||||||
|
)
|
||||||
.set(room_participant::ActiveModel {
|
.set(room_participant::ActiveModel {
|
||||||
location_kind: ActiveValue::set(Some(location_kind)),
|
location_kind: ActiveValue::set(Some(location_kind)),
|
||||||
location_project_id: ActiveValue::set(location_project_id),
|
location_project_id: ActiveValue::set(location_project_id),
|
||||||
|
@ -1437,11 +1487,21 @@ impl Database {
|
||||||
|
|
||||||
pub async fn connection_lost(
|
pub async fn connection_lost(
|
||||||
&self,
|
&self,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<RoomGuard<Vec<LeftProject>>> {
|
) -> Result<RoomGuard<Vec<LeftProject>>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let participant = room_participant::Entity::find()
|
let participant = room_participant::Entity::find()
|
||||||
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(
|
||||||
|
room_participant::Column::AnsweringConnectionId
|
||||||
|
.eq(connection.id as i32),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
room_participant::Column::AnsweringConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
),
|
||||||
|
)
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("not a participant in any room"))?;
|
.ok_or_else(|| anyhow!("not a participant in any room"))?;
|
||||||
|
@ -1456,11 +1516,25 @@ impl Database {
|
||||||
|
|
||||||
let collaborator_on_projects = project_collaborator::Entity::find()
|
let collaborator_on_projects = project_collaborator::Entity::find()
|
||||||
.find_also_related(project::Entity)
|
.find_also_related(project::Entity)
|
||||||
.filter(project_collaborator::Column::ConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
|
||||||
|
.add(
|
||||||
|
project_collaborator::Column::ConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
),
|
||||||
|
)
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
project_collaborator::Entity::delete_many()
|
project_collaborator::Entity::delete_many()
|
||||||
.filter(project_collaborator::Column::ConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
|
||||||
|
.add(
|
||||||
|
project_collaborator::Column::ConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
),
|
||||||
|
)
|
||||||
.exec(&*tx)
|
.exec(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -1473,20 +1547,30 @@ impl Database {
|
||||||
.await?;
|
.await?;
|
||||||
let connection_ids = collaborators
|
let connection_ids = collaborators
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|collaborator| ConnectionId(collaborator.connection_id as u32))
|
.map(|collaborator| ConnectionId {
|
||||||
|
id: collaborator.connection_id as u32,
|
||||||
|
epoch: collaborator.connection_epoch as u32,
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
left_projects.push(LeftProject {
|
left_projects.push(LeftProject {
|
||||||
id: project.id,
|
id: project.id,
|
||||||
host_user_id: project.host_user_id,
|
host_user_id: project.host_user_id,
|
||||||
host_connection_id: ConnectionId(project.host_connection_id as u32),
|
host_connection_id: ConnectionId {
|
||||||
|
id: project.host_connection_id as u32,
|
||||||
|
epoch: project.host_connection_epoch as u32,
|
||||||
|
},
|
||||||
connection_ids,
|
connection_ids,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
project::Entity::delete_many()
|
project::Entity::delete_many()
|
||||||
.filter(project::Column::HostConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(project::Column::HostConnectionId.eq(connection.id as i32))
|
||||||
|
.add(project::Column::HostConnectionEpoch.eq(connection.epoch as i32)),
|
||||||
|
)
|
||||||
.exec(&*tx)
|
.exec(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -1537,7 +1621,10 @@ impl Database {
|
||||||
let mut pending_participants = Vec::new();
|
let mut pending_participants = Vec::new();
|
||||||
while let Some(db_participant) = db_participants.next().await {
|
while let Some(db_participant) = db_participants.next().await {
|
||||||
let db_participant = db_participant?;
|
let db_participant = db_participant?;
|
||||||
if let Some(answering_connection_id) = db_participant.answering_connection_id {
|
if let Some((answering_connection_id, answering_connection_epoch)) = db_participant
|
||||||
|
.answering_connection_id
|
||||||
|
.zip(db_participant.answering_connection_epoch)
|
||||||
|
{
|
||||||
let location = match (
|
let location = match (
|
||||||
db_participant.location_kind,
|
db_participant.location_kind,
|
||||||
db_participant.location_project_id,
|
db_participant.location_project_id,
|
||||||
|
@ -1560,7 +1647,10 @@ impl Database {
|
||||||
answering_connection_id,
|
answering_connection_id,
|
||||||
proto::Participant {
|
proto::Participant {
|
||||||
user_id: db_participant.user_id.to_proto(),
|
user_id: db_participant.user_id.to_proto(),
|
||||||
peer_id: answering_connection_id as u32,
|
peer_id: Some(proto::PeerId {
|
||||||
|
epoch: answering_connection_epoch as u32,
|
||||||
|
id: answering_connection_id as u32,
|
||||||
|
}),
|
||||||
projects: Default::default(),
|
projects: Default::default(),
|
||||||
location: Some(proto::ParticipantLocation { variant: location }),
|
location: Some(proto::ParticipantLocation { variant: location }),
|
||||||
},
|
},
|
||||||
|
@ -1637,12 +1727,22 @@ impl Database {
|
||||||
pub async fn share_project(
|
pub async fn share_project(
|
||||||
&self,
|
&self,
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
worktrees: &[proto::WorktreeMetadata],
|
worktrees: &[proto::WorktreeMetadata],
|
||||||
) -> Result<RoomGuard<(ProjectId, proto::Room)>> {
|
) -> Result<RoomGuard<(ProjectId, proto::Room)>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let participant = room_participant::Entity::find()
|
let participant = room_participant::Entity::find()
|
||||||
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(
|
||||||
|
room_participant::Column::AnsweringConnectionId
|
||||||
|
.eq(connection.id as i32),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
room_participant::Column::AnsweringConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
),
|
||||||
|
)
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("could not find participant"))?;
|
.ok_or_else(|| anyhow!("could not find participant"))?;
|
||||||
|
@ -1653,8 +1753,8 @@ impl Database {
|
||||||
let project = project::ActiveModel {
|
let project = project::ActiveModel {
|
||||||
room_id: ActiveValue::set(participant.room_id),
|
room_id: ActiveValue::set(participant.room_id),
|
||||||
host_user_id: ActiveValue::set(participant.user_id),
|
host_user_id: ActiveValue::set(participant.user_id),
|
||||||
host_connection_id: ActiveValue::set(connection_id.0 as i32),
|
host_connection_id: ActiveValue::set(connection.id as i32),
|
||||||
host_connection_epoch: ActiveValue::set(self.epoch()),
|
host_connection_epoch: ActiveValue::set(connection.epoch as i32),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.insert(&*tx)
|
.insert(&*tx)
|
||||||
|
@ -1678,8 +1778,8 @@ impl Database {
|
||||||
|
|
||||||
project_collaborator::ActiveModel {
|
project_collaborator::ActiveModel {
|
||||||
project_id: ActiveValue::set(project.id),
|
project_id: ActiveValue::set(project.id),
|
||||||
connection_id: ActiveValue::set(connection_id.0 as i32),
|
connection_id: ActiveValue::set(connection.id as i32),
|
||||||
connection_epoch: ActiveValue::set(self.epoch()),
|
connection_epoch: ActiveValue::set(connection.epoch as i32),
|
||||||
user_id: ActiveValue::set(participant.user_id),
|
user_id: ActiveValue::set(participant.user_id),
|
||||||
replica_id: ActiveValue::set(ReplicaId(0)),
|
replica_id: ActiveValue::set(ReplicaId(0)),
|
||||||
is_host: ActiveValue::set(true),
|
is_host: ActiveValue::set(true),
|
||||||
|
@ -1697,7 +1797,7 @@ impl Database {
|
||||||
pub async fn unshare_project(
|
pub async fn unshare_project(
|
||||||
&self,
|
&self,
|
||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
|
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let guest_connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
|
let guest_connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
|
||||||
|
@ -1706,7 +1806,11 @@ impl Database {
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("project not found"))?;
|
.ok_or_else(|| anyhow!("project not found"))?;
|
||||||
if project.host_connection_id == connection_id.0 as i32 {
|
let host_connection = ConnectionId {
|
||||||
|
epoch: project.host_connection_epoch as u32,
|
||||||
|
id: project.host_connection_id as u32,
|
||||||
|
};
|
||||||
|
if host_connection == connection {
|
||||||
let room_id = project.room_id;
|
let room_id = project.room_id;
|
||||||
project::Entity::delete(project.into_active_model())
|
project::Entity::delete(project.into_active_model())
|
||||||
.exec(&*tx)
|
.exec(&*tx)
|
||||||
|
@ -1723,12 +1827,16 @@ impl Database {
|
||||||
pub async fn update_project(
|
pub async fn update_project(
|
||||||
&self,
|
&self,
|
||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
worktrees: &[proto::WorktreeMetadata],
|
worktrees: &[proto::WorktreeMetadata],
|
||||||
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
|
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let project = project::Entity::find_by_id(project_id)
|
let project = project::Entity::find_by_id(project_id)
|
||||||
.filter(project::Column::HostConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(project::Column::HostConnectionId.eq(connection.id as i32))
|
||||||
|
.add(project::Column::HostConnectionEpoch.eq(connection.epoch as i32)),
|
||||||
|
)
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("no such project"))?;
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
|
@ -1774,7 +1882,7 @@ impl Database {
|
||||||
pub async fn update_worktree(
|
pub async fn update_worktree(
|
||||||
&self,
|
&self,
|
||||||
update: &proto::UpdateWorktree,
|
update: &proto::UpdateWorktree,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<RoomGuard<Vec<ConnectionId>>> {
|
) -> Result<RoomGuard<Vec<ConnectionId>>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let project_id = ProjectId::from_proto(update.project_id);
|
let project_id = ProjectId::from_proto(update.project_id);
|
||||||
|
@ -1782,7 +1890,11 @@ impl Database {
|
||||||
|
|
||||||
// Ensure the update comes from the host.
|
// Ensure the update comes from the host.
|
||||||
let project = project::Entity::find_by_id(project_id)
|
let project = project::Entity::find_by_id(project_id)
|
||||||
.filter(project::Column::HostConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(project::Column::HostConnectionId.eq(connection.id as i32))
|
||||||
|
.add(project::Column::HostConnectionEpoch.eq(connection.epoch as i32)),
|
||||||
|
)
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("no such project"))?;
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
|
@ -1862,7 +1974,7 @@ impl Database {
|
||||||
pub async fn update_diagnostic_summary(
|
pub async fn update_diagnostic_summary(
|
||||||
&self,
|
&self,
|
||||||
update: &proto::UpdateDiagnosticSummary,
|
update: &proto::UpdateDiagnosticSummary,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<RoomGuard<Vec<ConnectionId>>> {
|
) -> Result<RoomGuard<Vec<ConnectionId>>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let project_id = ProjectId::from_proto(update.project_id);
|
let project_id = ProjectId::from_proto(update.project_id);
|
||||||
|
@ -1877,7 +1989,11 @@ impl Database {
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("no such project"))?;
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
if project.host_connection_id != connection_id.0 as i32 {
|
let host_connection = ConnectionId {
|
||||||
|
epoch: project.host_connection_epoch as u32,
|
||||||
|
id: project.host_connection_id as u32,
|
||||||
|
};
|
||||||
|
if host_connection != connection {
|
||||||
return Err(anyhow!("can't update a project hosted by someone else"))?;
|
return Err(anyhow!("can't update a project hosted by someone else"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1916,7 +2032,7 @@ impl Database {
|
||||||
pub async fn start_language_server(
|
pub async fn start_language_server(
|
||||||
&self,
|
&self,
|
||||||
update: &proto::StartLanguageServer,
|
update: &proto::StartLanguageServer,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<RoomGuard<Vec<ConnectionId>>> {
|
) -> Result<RoomGuard<Vec<ConnectionId>>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let project_id = ProjectId::from_proto(update.project_id);
|
let project_id = ProjectId::from_proto(update.project_id);
|
||||||
|
@ -1930,7 +2046,11 @@ impl Database {
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("no such project"))?;
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
if project.host_connection_id != connection_id.0 as i32 {
|
let host_connection = ConnectionId {
|
||||||
|
epoch: project.host_connection_epoch as u32,
|
||||||
|
id: project.host_connection_id as u32,
|
||||||
|
};
|
||||||
|
if host_connection != connection {
|
||||||
return Err(anyhow!("can't update a project hosted by someone else"))?;
|
return Err(anyhow!("can't update a project hosted by someone else"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1961,11 +2081,21 @@ impl Database {
|
||||||
pub async fn join_project(
|
pub async fn join_project(
|
||||||
&self,
|
&self,
|
||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<RoomGuard<(Project, ReplicaId)>> {
|
) -> Result<RoomGuard<(Project, ReplicaId)>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let participant = room_participant::Entity::find()
|
let participant = room_participant::Entity::find()
|
||||||
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.0 as i32))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(
|
||||||
|
room_participant::Column::AnsweringConnectionId
|
||||||
|
.eq(connection.id as i32),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
room_participant::Column::AnsweringConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
),
|
||||||
|
)
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("must join a room first"))?;
|
.ok_or_else(|| anyhow!("must join a room first"))?;
|
||||||
|
@ -1992,8 +2122,8 @@ impl Database {
|
||||||
}
|
}
|
||||||
let new_collaborator = project_collaborator::ActiveModel {
|
let new_collaborator = project_collaborator::ActiveModel {
|
||||||
project_id: ActiveValue::set(project_id),
|
project_id: ActiveValue::set(project_id),
|
||||||
connection_id: ActiveValue::set(connection_id.0 as i32),
|
connection_id: ActiveValue::set(connection.id as i32),
|
||||||
connection_epoch: ActiveValue::set(self.epoch()),
|
connection_epoch: ActiveValue::set(connection.epoch as i32),
|
||||||
user_id: ActiveValue::set(participant.user_id),
|
user_id: ActiveValue::set(participant.user_id),
|
||||||
replica_id: ActiveValue::set(replica_id),
|
replica_id: ActiveValue::set(replica_id),
|
||||||
is_host: ActiveValue::set(false),
|
is_host: ActiveValue::set(false),
|
||||||
|
@ -2095,14 +2225,18 @@ impl Database {
|
||||||
pub async fn leave_project(
|
pub async fn leave_project(
|
||||||
&self,
|
&self,
|
||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<RoomGuard<LeftProject>> {
|
) -> Result<RoomGuard<LeftProject>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let result = project_collaborator::Entity::delete_many()
|
let result = project_collaborator::Entity::delete_many()
|
||||||
.filter(
|
.filter(
|
||||||
project_collaborator::Column::ProjectId
|
Condition::all()
|
||||||
.eq(project_id)
|
.add(project_collaborator::Column::ProjectId.eq(project_id))
|
||||||
.and(project_collaborator::Column::ConnectionId.eq(connection_id.0 as i32)),
|
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
|
||||||
|
.add(
|
||||||
|
project_collaborator::Column::ConnectionEpoch
|
||||||
|
.eq(connection.epoch as i32),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.exec(&*tx)
|
.exec(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -2120,13 +2254,19 @@ impl Database {
|
||||||
.await?;
|
.await?;
|
||||||
let connection_ids = collaborators
|
let connection_ids = collaborators
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|collaborator| ConnectionId(collaborator.connection_id as u32))
|
.map(|collaborator| ConnectionId {
|
||||||
|
epoch: collaborator.connection_epoch as u32,
|
||||||
|
id: collaborator.connection_id as u32,
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let left_project = LeftProject {
|
let left_project = LeftProject {
|
||||||
id: project_id,
|
id: project_id,
|
||||||
host_user_id: project.host_user_id,
|
host_user_id: project.host_user_id,
|
||||||
host_connection_id: ConnectionId(project.host_connection_id as u32),
|
host_connection_id: ConnectionId {
|
||||||
|
epoch: project.host_connection_epoch as u32,
|
||||||
|
id: project.host_connection_id as u32,
|
||||||
|
},
|
||||||
connection_ids,
|
connection_ids,
|
||||||
};
|
};
|
||||||
Ok((project.room_id, left_project))
|
Ok((project.room_id, left_project))
|
||||||
|
@ -2137,7 +2277,7 @@ impl Database {
|
||||||
pub async fn project_collaborators(
|
pub async fn project_collaborators(
|
||||||
&self,
|
&self,
|
||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
connection_id: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<RoomGuard<Vec<project_collaborator::Model>>> {
|
) -> Result<RoomGuard<Vec<project_collaborator::Model>>> {
|
||||||
self.room_transaction(|tx| async move {
|
self.room_transaction(|tx| async move {
|
||||||
let project = project::Entity::find_by_id(project_id)
|
let project = project::Entity::find_by_id(project_id)
|
||||||
|
@ -2149,10 +2289,13 @@ impl Database {
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if collaborators
|
if collaborators.iter().any(|collaborator| {
|
||||||
.iter()
|
let collaborator_connection = ConnectionId {
|
||||||
.any(|collaborator| collaborator.connection_id == connection_id.0 as i32)
|
epoch: collaborator.connection_epoch as u32,
|
||||||
{
|
id: collaborator.connection_id as u32,
|
||||||
|
};
|
||||||
|
collaborator_connection == connection
|
||||||
|
}) {
|
||||||
Ok((project.room_id, collaborators))
|
Ok((project.room_id, collaborators))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("no such project"))?
|
Err(anyhow!("no such project"))?
|
||||||
|
@ -2166,30 +2309,42 @@ impl Database {
|
||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
connection_id: ConnectionId,
|
connection_id: ConnectionId,
|
||||||
) -> Result<RoomGuard<HashSet<ConnectionId>>> {
|
) -> Result<RoomGuard<HashSet<ConnectionId>>> {
|
||||||
self.room_transaction(|tx| async move {
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
enum QueryAs {
|
||||||
enum QueryAs {
|
Epoch,
|
||||||
ConnectionId,
|
Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromQueryResult)]
|
||||||
|
struct ProjectConnection {
|
||||||
|
epoch: i32,
|
||||||
|
id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.room_transaction(|tx| async move {
|
||||||
let project = project::Entity::find_by_id(project_id)
|
let project = project::Entity::find_by_id(project_id)
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("no such project"))?;
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
let mut db_connection_ids = project_collaborator::Entity::find()
|
let mut db_connections = project_collaborator::Entity::find()
|
||||||
.select_only()
|
.select_only()
|
||||||
|
.column_as(project_collaborator::Column::ConnectionId, QueryAs::Id)
|
||||||
.column_as(
|
.column_as(
|
||||||
project_collaborator::Column::ConnectionId,
|
project_collaborator::Column::ConnectionEpoch,
|
||||||
QueryAs::ConnectionId,
|
QueryAs::Epoch,
|
||||||
)
|
)
|
||||||
.filter(project_collaborator::Column::ProjectId.eq(project_id))
|
.filter(project_collaborator::Column::ProjectId.eq(project_id))
|
||||||
.into_values::<i32, QueryAs>()
|
.into_model::<ProjectConnection>()
|
||||||
.stream(&*tx)
|
.stream(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut connection_ids = HashSet::default();
|
let mut connection_ids = HashSet::default();
|
||||||
while let Some(connection_id) = db_connection_ids.next().await {
|
while let Some(connection) = db_connections.next().await {
|
||||||
connection_ids.insert(ConnectionId(connection_id? as u32));
|
let connection = connection?;
|
||||||
|
connection_ids.insert(ConnectionId {
|
||||||
|
epoch: connection.epoch as u32,
|
||||||
|
id: connection.id as u32,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if connection_ids.contains(&connection_id) {
|
if connection_ids.contains(&connection_id) {
|
||||||
|
@ -2208,27 +2363,39 @@ impl Database {
|
||||||
) -> Result<Vec<ConnectionId>> {
|
) -> Result<Vec<ConnectionId>> {
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||||
enum QueryAs {
|
enum QueryAs {
|
||||||
ConnectionId,
|
Epoch,
|
||||||
|
Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut db_guest_connection_ids = project_collaborator::Entity::find()
|
#[derive(Debug, FromQueryResult)]
|
||||||
|
struct ProjectConnection {
|
||||||
|
epoch: i32,
|
||||||
|
id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut db_guest_connections = project_collaborator::Entity::find()
|
||||||
.select_only()
|
.select_only()
|
||||||
|
.column_as(project_collaborator::Column::ConnectionId, QueryAs::Id)
|
||||||
.column_as(
|
.column_as(
|
||||||
project_collaborator::Column::ConnectionId,
|
project_collaborator::Column::ConnectionEpoch,
|
||||||
QueryAs::ConnectionId,
|
QueryAs::Epoch,
|
||||||
)
|
)
|
||||||
.filter(
|
.filter(
|
||||||
project_collaborator::Column::ProjectId
|
project_collaborator::Column::ProjectId
|
||||||
.eq(project_id)
|
.eq(project_id)
|
||||||
.and(project_collaborator::Column::IsHost.eq(false)),
|
.and(project_collaborator::Column::IsHost.eq(false)),
|
||||||
)
|
)
|
||||||
.into_values::<i32, QueryAs>()
|
.into_model::<ProjectConnection>()
|
||||||
.stream(tx)
|
.stream(tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut guest_connection_ids = Vec::new();
|
let mut guest_connection_ids = Vec::new();
|
||||||
while let Some(connection_id) = db_guest_connection_ids.next().await {
|
while let Some(connection) = db_guest_connections.next().await {
|
||||||
guest_connection_ids.push(ConnectionId(connection_id? as u32));
|
let connection = connection?;
|
||||||
|
guest_connection_ids.push(ConnectionId {
|
||||||
|
epoch: connection.epoch as u32,
|
||||||
|
id: connection.id as u32,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Ok(guest_connection_ids)
|
Ok(guest_connection_ids)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub struct Model {
|
||||||
pub room_id: RoomId,
|
pub room_id: RoomId,
|
||||||
pub host_user_id: UserId,
|
pub host_user_id: UserId,
|
||||||
pub host_connection_id: i32,
|
pub host_connection_id: i32,
|
||||||
pub host_connection_epoch: Uuid,
|
pub host_connection_epoch: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub struct Model {
|
||||||
pub id: ProjectCollaboratorId,
|
pub id: ProjectCollaboratorId,
|
||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
pub connection_id: i32,
|
pub connection_id: i32,
|
||||||
pub connection_epoch: Uuid,
|
pub connection_epoch: i32,
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub replica_id: ReplicaId,
|
pub replica_id: ReplicaId,
|
||||||
pub is_host: bool,
|
pub is_host: bool,
|
||||||
|
|
|
@ -9,14 +9,14 @@ pub struct Model {
|
||||||
pub room_id: RoomId,
|
pub room_id: RoomId,
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub answering_connection_id: Option<i32>,
|
pub answering_connection_id: Option<i32>,
|
||||||
pub answering_connection_epoch: Option<Uuid>,
|
pub answering_connection_epoch: Option<i32>,
|
||||||
pub answering_connection_lost: bool,
|
pub answering_connection_lost: bool,
|
||||||
pub location_kind: Option<i32>,
|
pub location_kind: Option<i32>,
|
||||||
pub location_project_id: Option<ProjectId>,
|
pub location_project_id: Option<ProjectId>,
|
||||||
pub initial_project_id: Option<ProjectId>,
|
pub initial_project_id: Option<ProjectId>,
|
||||||
pub calling_user_id: UserId,
|
pub calling_user_id: UserId,
|
||||||
pub calling_connection_id: i32,
|
pub calling_connection_id: i32,
|
||||||
pub calling_connection_epoch: Uuid,
|
pub calling_connection_epoch: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
@ -436,36 +436,44 @@ test_both_dbs!(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let room_id = RoomId::from_proto(
|
let room_id = RoomId::from_proto(
|
||||||
db.create_room(user1.user_id, ConnectionId(0), "")
|
db.create_room(user1.user_id, ConnectionId { epoch: 0, id: 0 }, "")
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.id,
|
.id,
|
||||||
);
|
);
|
||||||
db.call(room_id, user1.user_id, ConnectionId(0), user2.user_id, None)
|
db.call(
|
||||||
.await
|
room_id,
|
||||||
.unwrap();
|
user1.user_id,
|
||||||
db.join_room(room_id, user2.user_id, ConnectionId(1))
|
ConnectionId { epoch: 0, id: 0 },
|
||||||
|
user2.user_id,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
db.join_room(room_id, user2.user_id, ConnectionId { epoch: 0, id: 1 })
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
||||||
|
|
||||||
db.share_project(room_id, ConnectionId(1), &[])
|
db.share_project(room_id, ConnectionId { epoch: 0, id: 1 }, &[])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
|
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
|
||||||
|
|
||||||
db.share_project(room_id, ConnectionId(1), &[])
|
db.share_project(room_id, ConnectionId { epoch: 0, id: 1 }, &[])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||||
|
|
||||||
// Projects shared by admins aren't counted.
|
// Projects shared by admins aren't counted.
|
||||||
db.share_project(room_id, ConnectionId(0), &[])
|
db.share_project(room_id, ConnectionId { epoch: 0, id: 0 }, &[])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||||
|
|
||||||
db.leave_room(ConnectionId(1)).await.unwrap();
|
db.leave_room(ConnectionId { epoch: 0, id: 1 })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,8 +7,8 @@ use crate::{
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use call::{room, ActiveCall, ParticipantLocation, Room};
|
use call::{room, ActiveCall, ParticipantLocation, Room};
|
||||||
use client::{
|
use client::{
|
||||||
self, test::FakeHttpClient, Client, Connection, Credentials, EstablishConnectionError, PeerId,
|
self, proto::PeerId, test::FakeHttpClient, Client, Connection, Credentials,
|
||||||
User, UserStore, RECEIVE_TIMEOUT,
|
EstablishConnectionError, User, UserStore, RECEIVE_TIMEOUT,
|
||||||
};
|
};
|
||||||
use collections::{BTreeMap, HashMap, HashSet};
|
use collections::{BTreeMap, HashMap, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
|
@ -6066,7 +6066,7 @@ async fn test_random_collaboration(
|
||||||
.user_connection_ids(removed_guest_id)
|
.user_connection_ids(removed_guest_id)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
assert_eq!(user_connection_ids.len(), 1);
|
assert_eq!(user_connection_ids.len(), 1);
|
||||||
let removed_peer_id = PeerId(user_connection_ids[0].0);
|
let removed_peer_id = user_connection_ids[0].into();
|
||||||
let guest = clients.remove(guest_ix);
|
let guest = clients.remove(guest_ix);
|
||||||
op_start_signals.remove(guest_ix);
|
op_start_signals.remove(guest_ix);
|
||||||
server.forbid_connections();
|
server.forbid_connections();
|
||||||
|
@ -6115,7 +6115,7 @@ async fn test_random_collaboration(
|
||||||
.user_connection_ids(user_id)
|
.user_connection_ids(user_id)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
assert_eq!(user_connection_ids.len(), 1);
|
assert_eq!(user_connection_ids.len(), 1);
|
||||||
let peer_id = PeerId(user_connection_ids[0].0);
|
let peer_id = user_connection_ids[0].into();
|
||||||
server.disconnect_client(peer_id);
|
server.disconnect_client(peer_id);
|
||||||
deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
|
deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
|
||||||
operations += 1;
|
operations += 1;
|
||||||
|
@ -6429,7 +6429,7 @@ impl TestServer {
|
||||||
let connection_id = connection_id_rx.await.unwrap();
|
let connection_id = connection_id_rx.await.unwrap();
|
||||||
connection_killers
|
connection_killers
|
||||||
.lock()
|
.lock()
|
||||||
.insert(PeerId(connection_id.0), killed);
|
.insert(connection_id.into(), killed);
|
||||||
Ok(client_conn)
|
Ok(client_conn)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -170,7 +170,7 @@ where
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn new(app_state: Arc<AppState>, executor: Executor) -> Arc<Self> {
|
pub fn new(app_state: Arc<AppState>, executor: Executor) -> Arc<Self> {
|
||||||
let mut server = Self {
|
let mut server = Self {
|
||||||
peer: Peer::new(),
|
peer: Peer::new(0),
|
||||||
app_state,
|
app_state,
|
||||||
executor,
|
executor,
|
||||||
connection_pool: Default::default(),
|
connection_pool: Default::default(),
|
||||||
|
@ -457,9 +457,9 @@ impl Server {
|
||||||
move |duration| executor.sleep(duration)
|
move |duration| executor.sleep(duration)
|
||||||
});
|
});
|
||||||
|
|
||||||
tracing::info!(%user_id, %login, %connection_id, %address, "connection opened");
|
tracing::info!(%user_id, %login, ?connection_id, %address, "connection opened");
|
||||||
this.peer.send(connection_id, proto::Hello { peer_id: connection_id.0 })?;
|
this.peer.send(connection_id, proto::Hello { peer_id: Some(connection_id.into()) })?;
|
||||||
tracing::info!(%user_id, %login, %connection_id, %address, "sent hello message");
|
tracing::info!(%user_id, %login, ?connection_id, %address, "sent hello message");
|
||||||
|
|
||||||
if let Some(send_connection_id) = send_connection_id.take() {
|
if let Some(send_connection_id) = send_connection_id.take() {
|
||||||
let _ = send_connection_id.send(connection_id);
|
let _ = send_connection_id.send(connection_id);
|
||||||
|
@ -521,7 +521,7 @@ impl Server {
|
||||||
_ = teardown.changed().fuse() => return Ok(()),
|
_ = teardown.changed().fuse() => return Ok(()),
|
||||||
result = handle_io => {
|
result = handle_io => {
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
tracing::error!(?error, %user_id, %login, %connection_id, %address, "error handling I/O");
|
tracing::error!(?error, %user_id, %login, ?connection_id, %address, "error handling I/O");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -529,7 +529,7 @@ impl Server {
|
||||||
message = next_message => {
|
message = next_message => {
|
||||||
if let Some(message) = message {
|
if let Some(message) = message {
|
||||||
let type_name = message.payload_type_name();
|
let type_name = message.payload_type_name();
|
||||||
let span = tracing::info_span!("receive message", %user_id, %login, %connection_id, %address, type_name);
|
let span = tracing::info_span!("receive message", %user_id, %login, ?connection_id, %address, type_name);
|
||||||
let span_enter = span.enter();
|
let span_enter = span.enter();
|
||||||
if let Some(handler) = this.handlers.get(&message.payload_type_id()) {
|
if let Some(handler) = this.handlers.get(&message.payload_type_id()) {
|
||||||
let is_background = message.is_background();
|
let is_background = message.is_background();
|
||||||
|
@ -543,10 +543,10 @@ impl Server {
|
||||||
foreground_message_handlers.push(handle_message);
|
foreground_message_handlers.push(handle_message);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tracing::error!(%user_id, %login, %connection_id, %address, "no message handler");
|
tracing::error!(%user_id, %login, ?connection_id, %address, "no message handler");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tracing::info!(%user_id, %login, %connection_id, %address, "connection closed");
|
tracing::info!(%user_id, %login, ?connection_id, %address, "connection closed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -554,9 +554,9 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(foreground_message_handlers);
|
drop(foreground_message_handlers);
|
||||||
tracing::info!(%user_id, %login, %connection_id, %address, "signing out");
|
tracing::info!(%user_id, %login, ?connection_id, %address, "signing out");
|
||||||
if let Err(error) = sign_out(session, teardown, executor).await {
|
if let Err(error) = sign_out(session, teardown, executor).await {
|
||||||
tracing::error!(%user_id, %login, %connection_id, %address, ?error, "error signing out");
|
tracing::error!(%user_id, %login, ?connection_id, %address, ?error, "error signing out");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1128,12 +1128,18 @@ async fn join_project(
|
||||||
let collaborators = project
|
let collaborators = project
|
||||||
.collaborators
|
.collaborators
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|collaborator| collaborator.connection_id != session.connection_id.0 as i32)
|
.map(|collaborator| {
|
||||||
.map(|collaborator| proto::Collaborator {
|
let peer_id = proto::PeerId {
|
||||||
peer_id: collaborator.connection_id as u32,
|
epoch: collaborator.connection_epoch as u32,
|
||||||
replica_id: collaborator.replica_id.0 as u32,
|
id: collaborator.connection_id as u32,
|
||||||
user_id: collaborator.user_id.to_proto(),
|
};
|
||||||
|
proto::Collaborator {
|
||||||
|
peer_id: Some(peer_id),
|
||||||
|
replica_id: collaborator.replica_id.0 as u32,
|
||||||
|
user_id: collaborator.user_id.to_proto(),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
.filter(|collaborator| collaborator.peer_id != Some(session.connection_id.into()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let worktrees = project
|
let worktrees = project
|
||||||
.worktrees
|
.worktrees
|
||||||
|
@ -1150,11 +1156,11 @@ async fn join_project(
|
||||||
session
|
session
|
||||||
.peer
|
.peer
|
||||||
.send(
|
.send(
|
||||||
ConnectionId(collaborator.peer_id),
|
collaborator.peer_id.unwrap().into(),
|
||||||
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: session.connection_id.0,
|
peer_id: Some(session.connection_id.into()),
|
||||||
replica_id: replica_id.0 as u32,
|
replica_id: replica_id.0 as u32,
|
||||||
user_id: guest_user_id.to_proto(),
|
user_id: guest_user_id.to_proto(),
|
||||||
}),
|
}),
|
||||||
|
@ -1375,13 +1381,14 @@ where
|
||||||
.await
|
.await
|
||||||
.project_collaborators(project_id, session.connection_id)
|
.project_collaborators(project_id, session.connection_id)
|
||||||
.await?;
|
.await?;
|
||||||
ConnectionId(
|
let host = collaborators
|
||||||
collaborators
|
.iter()
|
||||||
.iter()
|
.find(|collaborator| collaborator.is_host)
|
||||||
.find(|collaborator| collaborator.is_host)
|
.ok_or_else(|| anyhow!("host not found"))?;
|
||||||
.ok_or_else(|| anyhow!("host not found"))?
|
ConnectionId {
|
||||||
.connection_id as u32,
|
epoch: host.connection_epoch as u32,
|
||||||
)
|
id: host.connection_id as u32,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let payload = session
|
let payload = session
|
||||||
|
@ -1409,7 +1416,10 @@ async fn save_buffer(
|
||||||
.iter()
|
.iter()
|
||||||
.find(|collaborator| collaborator.is_host)
|
.find(|collaborator| collaborator.is_host)
|
||||||
.ok_or_else(|| anyhow!("host not found"))?;
|
.ok_or_else(|| anyhow!("host not found"))?;
|
||||||
ConnectionId(host.connection_id as u32)
|
ConnectionId {
|
||||||
|
epoch: host.connection_epoch as u32,
|
||||||
|
id: host.connection_id as u32,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let response_payload = session
|
let response_payload = session
|
||||||
.peer
|
.peer
|
||||||
|
@ -1421,11 +1431,17 @@ async fn save_buffer(
|
||||||
.await
|
.await
|
||||||
.project_collaborators(project_id, session.connection_id)
|
.project_collaborators(project_id, session.connection_id)
|
||||||
.await?;
|
.await?;
|
||||||
collaborators
|
collaborators.retain(|collaborator| {
|
||||||
.retain(|collaborator| collaborator.connection_id != session.connection_id.0 as i32);
|
let collaborator_connection = ConnectionId {
|
||||||
let project_connection_ids = collaborators
|
epoch: collaborator.connection_epoch as u32,
|
||||||
.iter()
|
id: collaborator.connection_id as u32,
|
||||||
.map(|collaborator| ConnectionId(collaborator.connection_id as u32));
|
};
|
||||||
|
collaborator_connection != session.connection_id
|
||||||
|
});
|
||||||
|
let project_connection_ids = collaborators.iter().map(|collaborator| ConnectionId {
|
||||||
|
epoch: collaborator.connection_epoch as u32,
|
||||||
|
id: collaborator.connection_id as u32,
|
||||||
|
});
|
||||||
broadcast(host_connection_id, project_connection_ids, |conn_id| {
|
broadcast(host_connection_id, project_connection_ids, |conn_id| {
|
||||||
session
|
session
|
||||||
.peer
|
.peer
|
||||||
|
@ -1439,11 +1455,10 @@ async fn create_buffer_for_peer(
|
||||||
request: proto::CreateBufferForPeer,
|
request: proto::CreateBufferForPeer,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
session.peer.forward_send(
|
let peer_id = request.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?;
|
||||||
session.connection_id,
|
session
|
||||||
ConnectionId(request.peer_id),
|
.peer
|
||||||
request,
|
.forward_send(session.connection_id, peer_id.into(), request)?;
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1536,7 +1551,10 @@ async fn follow(
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let project_id = ProjectId::from_proto(request.project_id);
|
let project_id = ProjectId::from_proto(request.project_id);
|
||||||
let leader_id = ConnectionId(request.leader_id);
|
let leader_id = request
|
||||||
|
.leader_id
|
||||||
|
.ok_or_else(|| anyhow!("invalid leader id"))?
|
||||||
|
.into();
|
||||||
let follower_id = session.connection_id;
|
let follower_id = session.connection_id;
|
||||||
{
|
{
|
||||||
let project_connection_ids = session
|
let project_connection_ids = session
|
||||||
|
@ -1556,14 +1574,17 @@ async fn follow(
|
||||||
.await?;
|
.await?;
|
||||||
response_payload
|
response_payload
|
||||||
.views
|
.views
|
||||||
.retain(|view| view.leader_id != Some(follower_id.0));
|
.retain(|view| view.leader_id != Some(follower_id.into()));
|
||||||
response.send(response_payload)?;
|
response.send(response_payload)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unfollow(request: proto::Unfollow, session: Session) -> Result<()> {
|
async fn unfollow(request: proto::Unfollow, session: Session) -> Result<()> {
|
||||||
let project_id = ProjectId::from_proto(request.project_id);
|
let project_id = ProjectId::from_proto(request.project_id);
|
||||||
let leader_id = ConnectionId(request.leader_id);
|
let leader_id = request
|
||||||
|
.leader_id
|
||||||
|
.ok_or_else(|| anyhow!("invalid leader id"))?
|
||||||
|
.into();
|
||||||
let project_connection_ids = session
|
let project_connection_ids = session
|
||||||
.db()
|
.db()
|
||||||
.await
|
.await
|
||||||
|
@ -1592,12 +1613,16 @@ async fn update_followers(request: proto::UpdateFollowers, session: Session) ->
|
||||||
proto::update_followers::Variant::UpdateView(payload) => payload.leader_id,
|
proto::update_followers::Variant::UpdateView(payload) => payload.leader_id,
|
||||||
proto::update_followers::Variant::UpdateActiveView(payload) => payload.leader_id,
|
proto::update_followers::Variant::UpdateActiveView(payload) => payload.leader_id,
|
||||||
});
|
});
|
||||||
for follower_id in &request.follower_ids {
|
for follower_peer_id in request.follower_ids.iter().copied() {
|
||||||
let follower_id = ConnectionId(*follower_id);
|
let follower_connection_id = follower_peer_id.into();
|
||||||
if project_connection_ids.contains(&follower_id) && Some(follower_id.0) != leader_id {
|
if project_connection_ids.contains(&follower_connection_id)
|
||||||
session
|
&& Some(follower_peer_id) != leader_id
|
||||||
.peer
|
{
|
||||||
.forward_send(session.connection_id, follower_id, request.clone())?;
|
session.peer.forward_send(
|
||||||
|
session.connection_id,
|
||||||
|
follower_connection_id,
|
||||||
|
request.clone(),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1912,13 +1937,19 @@ fn contact_for_user(
|
||||||
|
|
||||||
fn room_updated(room: &proto::Room, peer: &Peer) {
|
fn room_updated(room: &proto::Room, peer: &Peer) {
|
||||||
for participant in &room.participants {
|
for participant in &room.participants {
|
||||||
peer.send(
|
if let Some(peer_id) = participant
|
||||||
ConnectionId(participant.peer_id),
|
.peer_id
|
||||||
proto::RoomUpdated {
|
.ok_or_else(|| anyhow!("invalid participant peer id"))
|
||||||
room: Some(room.clone()),
|
.trace_err()
|
||||||
},
|
{
|
||||||
)
|
peer.send(
|
||||||
.trace_err();
|
peer_id.into(),
|
||||||
|
proto::RoomUpdated {
|
||||||
|
room: Some(room.clone()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.trace_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2033,7 +2064,7 @@ fn project_left(project: &db::LeftProject, session: &Session) {
|
||||||
*connection_id,
|
*connection_id,
|
||||||
proto::RemoveProjectCollaborator {
|
proto::RemoveProjectCollaborator {
|
||||||
project_id: project.id.to_proto(),
|
project_id: project.id.to_proto(),
|
||||||
peer_id: session.connection_id.0,
|
peer_id: Some(session.connection_id.into()),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.trace_err();
|
.trace_err();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{contact_notification::ContactNotification, contacts_popover};
|
use crate::{contact_notification::ContactNotification, contacts_popover};
|
||||||
use call::{ActiveCall, ParticipantLocation};
|
use call::{ActiveCall, ParticipantLocation};
|
||||||
use client::{Authenticate, ContactEventKind, PeerId, User, UserStore};
|
use client::{proto::PeerId, Authenticate, ContactEventKind, User, UserStore};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use contacts_popover::ContactsPopover;
|
use contacts_popover::ContactsPopover;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -474,7 +474,7 @@ impl CollabTitlebarItem {
|
||||||
cx.dispatch_action(ToggleFollow(peer_id))
|
cx.dispatch_action(ToggleFollow(peer_id))
|
||||||
})
|
})
|
||||||
.with_tooltip::<ToggleFollow, _>(
|
.with_tooltip::<ToggleFollow, _>(
|
||||||
peer_id.0 as usize,
|
peer_id.as_u64() as usize,
|
||||||
if is_followed {
|
if is_followed {
|
||||||
format!("Unfollow {}", peer_github_login)
|
format!("Unfollow {}", peer_github_login)
|
||||||
} else {
|
} else {
|
||||||
|
@ -487,22 +487,24 @@ impl CollabTitlebarItem {
|
||||||
.boxed()
|
.boxed()
|
||||||
} else if let ParticipantLocation::SharedProject { project_id } = location {
|
} else if let ParticipantLocation::SharedProject { project_id } = location {
|
||||||
let user_id = user.id;
|
let user_id = user.id;
|
||||||
MouseEventHandler::<JoinProject>::new(peer_id.0 as usize, cx, move |_, _| content)
|
MouseEventHandler::<JoinProject>::new(peer_id.as_u64() as usize, cx, move |_, _| {
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
content
|
||||||
.on_click(MouseButton::Left, move |_, cx| {
|
})
|
||||||
cx.dispatch_action(JoinProject {
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
project_id,
|
.on_click(MouseButton::Left, move |_, cx| {
|
||||||
follow_user_id: user_id,
|
cx.dispatch_action(JoinProject {
|
||||||
})
|
project_id,
|
||||||
|
follow_user_id: user_id,
|
||||||
})
|
})
|
||||||
.with_tooltip::<JoinProject, _>(
|
})
|
||||||
peer_id.0 as usize,
|
.with_tooltip::<JoinProject, _>(
|
||||||
format!("Follow {} into external project", peer_github_login),
|
peer_id.as_u64() as usize,
|
||||||
Some(Box::new(FollowNextCollaborator)),
|
format!("Follow {} into external project", peer_github_login),
|
||||||
theme.tooltip.clone(),
|
Some(Box::new(FollowNextCollaborator)),
|
||||||
cx,
|
theme.tooltip.clone(),
|
||||||
)
|
cx,
|
||||||
.boxed()
|
)
|
||||||
|
.boxed()
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{mem, sync::Arc};
|
||||||
|
|
||||||
use crate::contacts_popover;
|
use crate::contacts_popover;
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use client::{Contact, PeerId, User, UserStore};
|
use client::{proto::PeerId, Contact, User, UserStore};
|
||||||
use editor::{Cancel, Editor};
|
use editor::{Cancel, Editor};
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -465,7 +465,7 @@ impl ContactList {
|
||||||
room.remote_participants()
|
room.remote_participants()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(peer_id, participant)| StringMatchCandidate {
|
.map(|(peer_id, participant)| StringMatchCandidate {
|
||||||
id: peer_id.0 as usize,
|
id: peer_id.as_u64() as usize,
|
||||||
string: participant.user.github_login.clone(),
|
string: participant.user.github_login.clone(),
|
||||||
char_bag: participant.user.github_login.chars().collect(),
|
char_bag: participant.user.github_login.chars().collect(),
|
||||||
}),
|
}),
|
||||||
|
@ -479,7 +479,7 @@ impl ContactList {
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
));
|
));
|
||||||
for mat in matches {
|
for mat in matches {
|
||||||
let peer_id = PeerId(mat.candidate_id as u32);
|
let peer_id = PeerId::from_u64(mat.candidate_id as u64);
|
||||||
let participant = &room.remote_participants()[&peer_id];
|
let participant = &room.remote_participants()[&peer_id];
|
||||||
participant_entries.push(ContactEntry::CallParticipant {
|
participant_entries.push(ContactEntry::CallParticipant {
|
||||||
user: participant.user.clone(),
|
user: participant.user.clone(),
|
||||||
|
@ -881,75 +881,80 @@ impl ContactList {
|
||||||
let baseline_offset =
|
let baseline_offset =
|
||||||
row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
|
row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
|
||||||
|
|
||||||
MouseEventHandler::<OpenSharedScreen>::new(peer_id.0 as usize, cx, |mouse_state, _| {
|
MouseEventHandler::<OpenSharedScreen>::new(
|
||||||
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
|
peer_id.as_u64() as usize,
|
||||||
let row = theme.project_row.style_for(mouse_state, is_selected);
|
cx,
|
||||||
|
|mouse_state, _| {
|
||||||
|
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
|
||||||
|
let row = theme.project_row.style_for(mouse_state, is_selected);
|
||||||
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
Stack::new()
|
Stack::new()
|
||||||
.with_child(
|
.with_child(
|
||||||
Canvas::new(move |bounds, _, cx| {
|
Canvas::new(move |bounds, _, cx| {
|
||||||
let start_x = bounds.min_x() + (bounds.width() / 2.)
|
let start_x = bounds.min_x() + (bounds.width() / 2.)
|
||||||
- (tree_branch.width / 2.);
|
- (tree_branch.width / 2.);
|
||||||
let end_x = bounds.max_x();
|
let end_x = bounds.max_x();
|
||||||
let start_y = bounds.min_y();
|
let start_y = bounds.min_y();
|
||||||
let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
|
let end_y =
|
||||||
|
bounds.min_y() + baseline_offset - (cap_height / 2.);
|
||||||
|
|
||||||
cx.scene.push_quad(gpui::Quad {
|
cx.scene.push_quad(gpui::Quad {
|
||||||
bounds: RectF::from_points(
|
bounds: RectF::from_points(
|
||||||
vec2f(start_x, start_y),
|
vec2f(start_x, start_y),
|
||||||
vec2f(
|
vec2f(
|
||||||
start_x + tree_branch.width,
|
start_x + tree_branch.width,
|
||||||
if is_last { end_y } else { bounds.max_y() },
|
if is_last { end_y } else { bounds.max_y() },
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
background: Some(tree_branch.color),
|
||||||
background: Some(tree_branch.color),
|
border: gpui::Border::default(),
|
||||||
border: gpui::Border::default(),
|
corner_radius: 0.,
|
||||||
corner_radius: 0.,
|
});
|
||||||
});
|
cx.scene.push_quad(gpui::Quad {
|
||||||
cx.scene.push_quad(gpui::Quad {
|
bounds: RectF::from_points(
|
||||||
bounds: RectF::from_points(
|
vec2f(start_x, end_y),
|
||||||
vec2f(start_x, end_y),
|
vec2f(end_x, end_y + tree_branch.width),
|
||||||
vec2f(end_x, end_y + tree_branch.width),
|
),
|
||||||
),
|
background: Some(tree_branch.color),
|
||||||
background: Some(tree_branch.color),
|
border: gpui::Border::default(),
|
||||||
border: gpui::Border::default(),
|
corner_radius: 0.,
|
||||||
corner_radius: 0.,
|
});
|
||||||
});
|
})
|
||||||
})
|
.boxed(),
|
||||||
|
)
|
||||||
|
.constrained()
|
||||||
|
.with_width(host_avatar_height)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.constrained()
|
.with_child(
|
||||||
.with_width(host_avatar_height)
|
Svg::new("icons/disable_screen_sharing_12.svg")
|
||||||
.boxed(),
|
.with_color(row.icon.color)
|
||||||
)
|
.constrained()
|
||||||
.with_child(
|
.with_width(row.icon.width)
|
||||||
Svg::new("icons/disable_screen_sharing_12.svg")
|
.aligned()
|
||||||
.with_color(row.icon.color)
|
.left()
|
||||||
.constrained()
|
.contained()
|
||||||
.with_width(row.icon.width)
|
.with_style(row.icon.container)
|
||||||
.aligned()
|
.boxed(),
|
||||||
.left()
|
)
|
||||||
.contained()
|
.with_child(
|
||||||
.with_style(row.icon.container)
|
Label::new("Screen".into(), row.name.text.clone())
|
||||||
.boxed(),
|
.aligned()
|
||||||
)
|
.left()
|
||||||
.with_child(
|
.contained()
|
||||||
Label::new("Screen".into(), row.name.text.clone())
|
.with_style(row.name.container)
|
||||||
.aligned()
|
.flex(1., false)
|
||||||
.left()
|
.boxed(),
|
||||||
.contained()
|
)
|
||||||
.with_style(row.name.container)
|
.constrained()
|
||||||
.flex(1., false)
|
.with_height(theme.row_height)
|
||||||
.boxed(),
|
.contained()
|
||||||
)
|
.with_style(row.container)
|
||||||
.constrained()
|
.boxed()
|
||||||
.with_height(theme.row_height)
|
},
|
||||||
.contained()
|
)
|
||||||
.with_style(row.container)
|
|
||||||
.boxed()
|
|
||||||
})
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.on_click(MouseButton::Left, move |_, cx| {
|
.on_click(MouseButton::Left, move |_, cx| {
|
||||||
cx.dispatch_action(OpenSharedScreen { peer_id });
|
cx.dispatch_action(OpenSharedScreen { peer_id });
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use client::{proto, PeerId};
|
use client::proto::{self, PeerId};
|
||||||
use gpui::{AppContext, AsyncAppContext, ModelHandle};
|
use gpui::{AppContext, AsyncAppContext, ModelHandle};
|
||||||
use language::{
|
use language::{
|
||||||
point_from_lsp, point_to_lsp,
|
point_from_lsp, point_to_lsp,
|
||||||
|
|
|
@ -7,7 +7,7 @@ pub mod worktree;
|
||||||
mod project_tests;
|
mod project_tests;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use client::{proto, Client, PeerId, TypedEnvelope, UserStore};
|
use client::{proto, Client, TypedEnvelope, UserStore};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{hash_map, BTreeMap, HashMap, HashSet};
|
use collections::{hash_map, BTreeMap, HashMap, HashSet};
|
||||||
use futures::{
|
use futures::{
|
||||||
|
@ -15,7 +15,6 @@ use futures::{
|
||||||
future::Shared,
|
future::Shared,
|
||||||
AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt,
|
AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
||||||
MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle,
|
MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle,
|
||||||
|
@ -103,11 +102,11 @@ pub struct Project {
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
client_state: Option<ProjectClientState>,
|
client_state: Option<ProjectClientState>,
|
||||||
collaborators: HashMap<PeerId, Collaborator>,
|
collaborators: HashMap<proto::PeerId, Collaborator>,
|
||||||
client_subscriptions: Vec<client::Subscription>,
|
client_subscriptions: Vec<client::Subscription>,
|
||||||
_subscriptions: Vec<gpui::Subscription>,
|
_subscriptions: Vec<gpui::Subscription>,
|
||||||
opened_buffer: (watch::Sender<()>, watch::Receiver<()>),
|
opened_buffer: (watch::Sender<()>, watch::Receiver<()>),
|
||||||
shared_buffers: HashMap<PeerId, HashSet<u64>>,
|
shared_buffers: HashMap<proto::PeerId, HashSet<u64>>,
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
loading_buffers: HashMap<
|
loading_buffers: HashMap<
|
||||||
ProjectPath,
|
ProjectPath,
|
||||||
|
@ -164,7 +163,7 @@ enum ProjectClientState {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Collaborator {
|
pub struct Collaborator {
|
||||||
pub peer_id: PeerId,
|
pub peer_id: proto::PeerId,
|
||||||
pub replica_id: ReplicaId,
|
pub replica_id: ReplicaId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +184,7 @@ pub enum Event {
|
||||||
},
|
},
|
||||||
RemoteIdChanged(Option<u64>),
|
RemoteIdChanged(Option<u64>),
|
||||||
DisconnectedFromHost,
|
DisconnectedFromHost,
|
||||||
CollaboratorLeft(PeerId),
|
CollaboratorLeft(proto::PeerId),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum LanguageServerState {
|
pub enum LanguageServerState {
|
||||||
|
@ -555,7 +554,7 @@ impl Project {
|
||||||
.await?;
|
.await?;
|
||||||
let mut collaborators = HashMap::default();
|
let mut collaborators = HashMap::default();
|
||||||
for message in response.collaborators {
|
for message in response.collaborators {
|
||||||
let collaborator = Collaborator::from_proto(message);
|
let collaborator = Collaborator::from_proto(message)?;
|
||||||
collaborators.insert(collaborator.peer_id, collaborator);
|
collaborators.insert(collaborator.peer_id, collaborator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -754,7 +753,7 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
|
pub fn collaborators(&self) -> &HashMap<proto::PeerId, Collaborator> {
|
||||||
&self.collaborators
|
&self.collaborators
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4605,7 +4604,7 @@ impl Project {
|
||||||
.take()
|
.take()
|
||||||
.ok_or_else(|| anyhow!("empty collaborator"))?;
|
.ok_or_else(|| anyhow!("empty collaborator"))?;
|
||||||
|
|
||||||
let collaborator = Collaborator::from_proto(collaborator);
|
let collaborator = Collaborator::from_proto(collaborator)?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.collaborators
|
this.collaborators
|
||||||
.insert(collaborator.peer_id, collaborator);
|
.insert(collaborator.peer_id, collaborator);
|
||||||
|
@ -4622,7 +4621,10 @@ impl Project {
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
let peer_id = PeerId(envelope.payload.peer_id);
|
let peer_id = envelope
|
||||||
|
.payload
|
||||||
|
.peer_id
|
||||||
|
.ok_or_else(|| anyhow!("invalid peer id"))?;
|
||||||
let replica_id = this
|
let replica_id = this
|
||||||
.collaborators
|
.collaborators
|
||||||
.remove(&peer_id)
|
.remove(&peer_id)
|
||||||
|
@ -5489,7 +5491,7 @@ impl Project {
|
||||||
fn serialize_project_transaction_for_peer(
|
fn serialize_project_transaction_for_peer(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_transaction: ProjectTransaction,
|
project_transaction: ProjectTransaction,
|
||||||
peer_id: PeerId,
|
peer_id: proto::PeerId,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> proto::ProjectTransaction {
|
) -> proto::ProjectTransaction {
|
||||||
let mut serialized_transaction = proto::ProjectTransaction {
|
let mut serialized_transaction = proto::ProjectTransaction {
|
||||||
|
@ -5545,7 +5547,7 @@ impl Project {
|
||||||
fn create_buffer_for_peer(
|
fn create_buffer_for_peer(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &ModelHandle<Buffer>,
|
buffer: &ModelHandle<Buffer>,
|
||||||
peer_id: PeerId,
|
peer_id: proto::PeerId,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> u64 {
|
) -> u64 {
|
||||||
let buffer_id = buffer.read(cx).remote_id();
|
let buffer_id = buffer.read(cx).remote_id();
|
||||||
|
@ -5563,7 +5565,7 @@ impl Project {
|
||||||
|
|
||||||
client.send(proto::CreateBufferForPeer {
|
client.send(proto::CreateBufferForPeer {
|
||||||
project_id,
|
project_id,
|
||||||
peer_id: peer_id.0,
|
peer_id: Some(peer_id),
|
||||||
variant: Some(proto::create_buffer_for_peer::Variant::State(state)),
|
variant: Some(proto::create_buffer_for_peer::Variant::State(state)),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -5580,7 +5582,7 @@ impl Project {
|
||||||
let is_last = operations.is_empty();
|
let is_last = operations.is_empty();
|
||||||
client.send(proto::CreateBufferForPeer {
|
client.send(proto::CreateBufferForPeer {
|
||||||
project_id,
|
project_id,
|
||||||
peer_id: peer_id.0,
|
peer_id: Some(peer_id),
|
||||||
variant: Some(proto::create_buffer_for_peer::Variant::Chunk(
|
variant: Some(proto::create_buffer_for_peer::Variant::Chunk(
|
||||||
proto::BufferChunk {
|
proto::BufferChunk {
|
||||||
buffer_id,
|
buffer_id,
|
||||||
|
@ -6036,11 +6038,11 @@ impl Entity for Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collaborator {
|
impl Collaborator {
|
||||||
fn from_proto(message: proto::Collaborator) -> Self {
|
fn from_proto(message: proto::Collaborator) -> Result<Self> {
|
||||||
Self {
|
Ok(Self {
|
||||||
peer_id: PeerId(message.peer_id),
|
peer_id: message.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?,
|
||||||
replica_id: message.replica_id as ReplicaId,
|
replica_id: message.replica_id as ReplicaId,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
package zed.messages;
|
package zed.messages;
|
||||||
|
|
||||||
|
message PeerId {
|
||||||
|
uint32 epoch = 1;
|
||||||
|
uint32 id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message Envelope {
|
message Envelope {
|
||||||
uint32 id = 1;
|
uint32 id = 1;
|
||||||
optional uint32 responding_to = 2;
|
optional uint32 responding_to = 2;
|
||||||
optional uint32 original_sender_id = 3;
|
PeerId original_sender_id = 3;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
Hello hello = 4;
|
Hello hello = 4;
|
||||||
Ack ack = 5;
|
Ack ack = 5;
|
||||||
|
@ -125,7 +130,7 @@ message Envelope {
|
||||||
// Messages
|
// Messages
|
||||||
|
|
||||||
message Hello {
|
message Hello {
|
||||||
uint32 peer_id = 1;
|
PeerId peer_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Ping {}
|
message Ping {}
|
||||||
|
@ -167,7 +172,7 @@ message Room {
|
||||||
|
|
||||||
message Participant {
|
message Participant {
|
||||||
uint64 user_id = 1;
|
uint64 user_id = 1;
|
||||||
uint32 peer_id = 2;
|
PeerId peer_id = 2;
|
||||||
repeated ParticipantProject projects = 3;
|
repeated ParticipantProject projects = 3;
|
||||||
ParticipantLocation location = 4;
|
ParticipantLocation location = 4;
|
||||||
}
|
}
|
||||||
|
@ -319,7 +324,7 @@ message AddProjectCollaborator {
|
||||||
|
|
||||||
message RemoveProjectCollaborator {
|
message RemoveProjectCollaborator {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint32 peer_id = 2;
|
PeerId peer_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetDefinition {
|
message GetDefinition {
|
||||||
|
@ -438,7 +443,7 @@ message OpenBufferResponse {
|
||||||
|
|
||||||
message CreateBufferForPeer {
|
message CreateBufferForPeer {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint32 peer_id = 2;
|
PeerId peer_id = 2;
|
||||||
oneof variant {
|
oneof variant {
|
||||||
BufferState state = 3;
|
BufferState state = 3;
|
||||||
BufferChunk chunk = 4;
|
BufferChunk chunk = 4;
|
||||||
|
@ -794,7 +799,7 @@ message UpdateDiagnostics {
|
||||||
|
|
||||||
message Follow {
|
message Follow {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint32 leader_id = 2;
|
PeerId leader_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message FollowResponse {
|
message FollowResponse {
|
||||||
|
@ -804,7 +809,7 @@ message FollowResponse {
|
||||||
|
|
||||||
message UpdateFollowers {
|
message UpdateFollowers {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
repeated uint32 follower_ids = 2;
|
repeated PeerId follower_ids = 2;
|
||||||
oneof variant {
|
oneof variant {
|
||||||
UpdateActiveView update_active_view = 3;
|
UpdateActiveView update_active_view = 3;
|
||||||
View create_view = 4;
|
View create_view = 4;
|
||||||
|
@ -814,7 +819,7 @@ message UpdateFollowers {
|
||||||
|
|
||||||
message Unfollow {
|
message Unfollow {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint32 leader_id = 2;
|
PeerId leader_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetPrivateUserInfo {}
|
message GetPrivateUserInfo {}
|
||||||
|
@ -828,12 +833,12 @@ message GetPrivateUserInfoResponse {
|
||||||
|
|
||||||
message UpdateActiveView {
|
message UpdateActiveView {
|
||||||
optional uint64 id = 1;
|
optional uint64 id = 1;
|
||||||
optional uint32 leader_id = 2;
|
optional PeerId leader_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateView {
|
message UpdateView {
|
||||||
uint64 id = 1;
|
uint64 id = 1;
|
||||||
optional uint32 leader_id = 2;
|
optional PeerId leader_id = 2;
|
||||||
|
|
||||||
oneof variant {
|
oneof variant {
|
||||||
Editor editor = 3;
|
Editor editor = 3;
|
||||||
|
@ -849,7 +854,7 @@ message UpdateView {
|
||||||
|
|
||||||
message View {
|
message View {
|
||||||
uint64 id = 1;
|
uint64 id = 1;
|
||||||
optional uint32 leader_id = 2;
|
optional PeerId leader_id = 2;
|
||||||
|
|
||||||
oneof variant {
|
oneof variant {
|
||||||
Editor editor = 3;
|
Editor editor = 3;
|
||||||
|
@ -865,7 +870,7 @@ message View {
|
||||||
}
|
}
|
||||||
|
|
||||||
message Collaborator {
|
message Collaborator {
|
||||||
uint32 peer_id = 1;
|
PeerId peer_id = 1;
|
||||||
uint32 replica_id = 2;
|
uint32 replica_id = 2;
|
||||||
uint64 user_id = 3;
|
uint64 user_id = 3;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,10 @@ macro_rules! messages {
|
||||||
$(Some(envelope::Payload::$name(payload)) => {
|
$(Some(envelope::Payload::$name(payload)) => {
|
||||||
Some(Box::new(TypedEnvelope {
|
Some(Box::new(TypedEnvelope {
|
||||||
sender_id,
|
sender_id,
|
||||||
original_sender_id: envelope.original_sender_id.map(PeerId),
|
original_sender_id: envelope.original_sender_id.map(|original_sender| PeerId {
|
||||||
|
epoch: original_sender.epoch,
|
||||||
|
id: original_sender.id
|
||||||
|
}),
|
||||||
message_id: envelope.id,
|
message_id: envelope.id,
|
||||||
payload,
|
payload,
|
||||||
}))
|
}))
|
||||||
|
@ -24,7 +27,7 @@ macro_rules! messages {
|
||||||
self,
|
self,
|
||||||
id: u32,
|
id: u32,
|
||||||
responding_to: Option<u32>,
|
responding_to: Option<u32>,
|
||||||
original_sender_id: Option<u32>,
|
original_sender_id: Option<PeerId>,
|
||||||
) -> Envelope {
|
) -> Envelope {
|
||||||
Envelope {
|
Envelope {
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{
|
use super::{
|
||||||
proto::{self, AnyTypedEnvelope, EnvelopedMessage, MessageStream, RequestMessage},
|
proto::{self, AnyTypedEnvelope, EnvelopedMessage, MessageStream, PeerId, RequestMessage},
|
||||||
Connection,
|
Connection,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
@ -11,9 +11,8 @@ use futures::{
|
||||||
};
|
};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use serde::{ser::SerializeStruct, Serialize};
|
use serde::{ser::SerializeStruct, Serialize};
|
||||||
use std::sync::atomic::Ordering::SeqCst;
|
use std::{fmt, sync::atomic::Ordering::SeqCst};
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
sync::{
|
sync::{
|
||||||
|
@ -25,20 +24,32 @@ use std::{
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize)]
|
||||||
pub struct ConnectionId(pub u32);
|
pub struct ConnectionId {
|
||||||
|
pub epoch: u32,
|
||||||
|
pub id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for ConnectionId {
|
impl Into<PeerId> for ConnectionId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn into(self) -> PeerId {
|
||||||
self.0.fmt(f)
|
PeerId {
|
||||||
|
epoch: self.epoch,
|
||||||
|
id: self.id,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
impl From<PeerId> for ConnectionId {
|
||||||
pub struct PeerId(pub u32);
|
fn from(peer_id: PeerId) -> Self {
|
||||||
|
Self {
|
||||||
|
epoch: peer_id.epoch,
|
||||||
|
id: peer_id.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for PeerId {
|
impl fmt::Display for ConnectionId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
self.0.fmt(f)
|
write!(f, "{}/{}", self.epoch, self.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +81,7 @@ pub struct TypedEnvelope<T> {
|
||||||
impl<T> TypedEnvelope<T> {
|
impl<T> TypedEnvelope<T> {
|
||||||
pub fn original_sender_id(&self) -> Result<PeerId> {
|
pub fn original_sender_id(&self) -> Result<PeerId> {
|
||||||
self.original_sender_id
|
self.original_sender_id
|
||||||
|
.clone()
|
||||||
.ok_or_else(|| anyhow!("missing original_sender_id"))
|
.ok_or_else(|| anyhow!("missing original_sender_id"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,6 +97,7 @@ impl<T: RequestMessage> TypedEnvelope<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Peer {
|
pub struct Peer {
|
||||||
|
epoch: u32,
|
||||||
pub connections: RwLock<HashMap<ConnectionId, ConnectionState>>,
|
pub connections: RwLock<HashMap<ConnectionId, ConnectionState>>,
|
||||||
next_connection_id: AtomicU32,
|
next_connection_id: AtomicU32,
|
||||||
}
|
}
|
||||||
|
@ -105,8 +118,9 @@ const WRITE_TIMEOUT: Duration = Duration::from_secs(2);
|
||||||
pub const RECEIVE_TIMEOUT: Duration = Duration::from_secs(5);
|
pub const RECEIVE_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
impl Peer {
|
impl Peer {
|
||||||
pub fn new() -> Arc<Self> {
|
pub fn new(epoch: u32) -> Arc<Self> {
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
|
epoch,
|
||||||
connections: Default::default(),
|
connections: Default::default(),
|
||||||
next_connection_id: Default::default(),
|
next_connection_id: Default::default(),
|
||||||
})
|
})
|
||||||
|
@ -138,7 +152,10 @@ impl Peer {
|
||||||
let (mut incoming_tx, incoming_rx) = mpsc::channel(INCOMING_BUFFER_SIZE);
|
let (mut incoming_tx, incoming_rx) = mpsc::channel(INCOMING_BUFFER_SIZE);
|
||||||
let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded();
|
let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded();
|
||||||
|
|
||||||
let connection_id = ConnectionId(self.next_connection_id.fetch_add(1, SeqCst));
|
let connection_id = ConnectionId {
|
||||||
|
epoch: self.epoch,
|
||||||
|
id: self.next_connection_id.fetch_add(1, SeqCst),
|
||||||
|
};
|
||||||
let connection_state = ConnectionState {
|
let connection_state = ConnectionState {
|
||||||
outgoing_tx,
|
outgoing_tx,
|
||||||
next_message_id: Default::default(),
|
next_message_id: Default::default(),
|
||||||
|
@ -150,12 +167,12 @@ impl Peer {
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
let response_channels = connection_state.response_channels.clone();
|
let response_channels = connection_state.response_channels.clone();
|
||||||
let handle_io = async move {
|
let handle_io = async move {
|
||||||
tracing::debug!(%connection_id, "handle io future: start");
|
tracing::debug!(?connection_id, "handle io future: start");
|
||||||
|
|
||||||
let _end_connection = util::defer(|| {
|
let _end_connection = util::defer(|| {
|
||||||
response_channels.lock().take();
|
response_channels.lock().take();
|
||||||
this.connections.write().remove(&connection_id);
|
this.connections.write().remove(&connection_id);
|
||||||
tracing::debug!(%connection_id, "handle io future: end");
|
tracing::debug!(?connection_id, "handle io future: end");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send messages on this frequency so the connection isn't closed.
|
// Send messages on this frequency so the connection isn't closed.
|
||||||
|
@ -167,68 +184,68 @@ impl Peer {
|
||||||
futures::pin_mut!(receive_timeout);
|
futures::pin_mut!(receive_timeout);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tracing::debug!(%connection_id, "outer loop iteration start");
|
tracing::debug!(?connection_id, "outer loop iteration start");
|
||||||
let read_message = reader.read().fuse();
|
let read_message = reader.read().fuse();
|
||||||
futures::pin_mut!(read_message);
|
futures::pin_mut!(read_message);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tracing::debug!(%connection_id, "inner loop iteration start");
|
tracing::debug!(?connection_id, "inner loop iteration start");
|
||||||
futures::select_biased! {
|
futures::select_biased! {
|
||||||
outgoing = outgoing_rx.next().fuse() => match outgoing {
|
outgoing = outgoing_rx.next().fuse() => match outgoing {
|
||||||
Some(outgoing) => {
|
Some(outgoing) => {
|
||||||
tracing::debug!(%connection_id, "outgoing rpc message: writing");
|
tracing::debug!(?connection_id, "outgoing rpc message: writing");
|
||||||
futures::select_biased! {
|
futures::select_biased! {
|
||||||
result = writer.write(outgoing).fuse() => {
|
result = writer.write(outgoing).fuse() => {
|
||||||
tracing::debug!(%connection_id, "outgoing rpc message: done writing");
|
tracing::debug!(?connection_id, "outgoing rpc message: done writing");
|
||||||
result.context("failed to write RPC message")?;
|
result.context("failed to write RPC message")?;
|
||||||
tracing::debug!(%connection_id, "keepalive interval: resetting after sending message");
|
tracing::debug!(?connection_id, "keepalive interval: resetting after sending message");
|
||||||
keepalive_timer.set(create_timer(KEEPALIVE_INTERVAL).fuse());
|
keepalive_timer.set(create_timer(KEEPALIVE_INTERVAL).fuse());
|
||||||
}
|
}
|
||||||
_ = create_timer(WRITE_TIMEOUT).fuse() => {
|
_ = create_timer(WRITE_TIMEOUT).fuse() => {
|
||||||
tracing::debug!(%connection_id, "outgoing rpc message: writing timed out");
|
tracing::debug!(?connection_id, "outgoing rpc message: writing timed out");
|
||||||
Err(anyhow!("timed out writing message"))?;
|
Err(anyhow!("timed out writing message"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
tracing::debug!(%connection_id, "outgoing rpc message: channel closed");
|
tracing::debug!(?connection_id, "outgoing rpc message: channel closed");
|
||||||
return Ok(())
|
return Ok(())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_ = keepalive_timer => {
|
_ = keepalive_timer => {
|
||||||
tracing::debug!(%connection_id, "keepalive interval: pinging");
|
tracing::debug!(?connection_id, "keepalive interval: pinging");
|
||||||
futures::select_biased! {
|
futures::select_biased! {
|
||||||
result = writer.write(proto::Message::Ping).fuse() => {
|
result = writer.write(proto::Message::Ping).fuse() => {
|
||||||
tracing::debug!(%connection_id, "keepalive interval: done pinging");
|
tracing::debug!(?connection_id, "keepalive interval: done pinging");
|
||||||
result.context("failed to send keepalive")?;
|
result.context("failed to send keepalive")?;
|
||||||
tracing::debug!(%connection_id, "keepalive interval: resetting after pinging");
|
tracing::debug!(?connection_id, "keepalive interval: resetting after pinging");
|
||||||
keepalive_timer.set(create_timer(KEEPALIVE_INTERVAL).fuse());
|
keepalive_timer.set(create_timer(KEEPALIVE_INTERVAL).fuse());
|
||||||
}
|
}
|
||||||
_ = create_timer(WRITE_TIMEOUT).fuse() => {
|
_ = create_timer(WRITE_TIMEOUT).fuse() => {
|
||||||
tracing::debug!(%connection_id, "keepalive interval: pinging timed out");
|
tracing::debug!(?connection_id, "keepalive interval: pinging timed out");
|
||||||
Err(anyhow!("timed out sending keepalive"))?;
|
Err(anyhow!("timed out sending keepalive"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
incoming = read_message => {
|
incoming = read_message => {
|
||||||
let incoming = incoming.context("error reading rpc message from socket")?;
|
let incoming = incoming.context("error reading rpc message from socket")?;
|
||||||
tracing::debug!(%connection_id, "incoming rpc message: received");
|
tracing::debug!(?connection_id, "incoming rpc message: received");
|
||||||
tracing::debug!(%connection_id, "receive timeout: resetting");
|
tracing::debug!(?connection_id, "receive timeout: resetting");
|
||||||
receive_timeout.set(create_timer(RECEIVE_TIMEOUT).fuse());
|
receive_timeout.set(create_timer(RECEIVE_TIMEOUT).fuse());
|
||||||
if let proto::Message::Envelope(incoming) = incoming {
|
if let proto::Message::Envelope(incoming) = incoming {
|
||||||
tracing::debug!(%connection_id, "incoming rpc message: processing");
|
tracing::debug!(?connection_id, "incoming rpc message: processing");
|
||||||
futures::select_biased! {
|
futures::select_biased! {
|
||||||
result = incoming_tx.send(incoming).fuse() => match result {
|
result = incoming_tx.send(incoming).fuse() => match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tracing::debug!(%connection_id, "incoming rpc message: processed");
|
tracing::debug!(?connection_id, "incoming rpc message: processed");
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
tracing::debug!(%connection_id, "incoming rpc message: channel closed");
|
tracing::debug!(?connection_id, "incoming rpc message: channel closed");
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = create_timer(WRITE_TIMEOUT).fuse() => {
|
_ = create_timer(WRITE_TIMEOUT).fuse() => {
|
||||||
tracing::debug!(%connection_id, "incoming rpc message: processing timed out");
|
tracing::debug!(?connection_id, "incoming rpc message: processing timed out");
|
||||||
Err(anyhow!("timed out processing incoming message"))?
|
Err(anyhow!("timed out processing incoming message"))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,7 +253,7 @@ impl Peer {
|
||||||
break;
|
break;
|
||||||
},
|
},
|
||||||
_ = receive_timeout => {
|
_ = receive_timeout => {
|
||||||
tracing::debug!(%connection_id, "receive timeout: delay between messages too long");
|
tracing::debug!(?connection_id, "receive timeout: delay between messages too long");
|
||||||
Err(anyhow!("delay between messages too long"))?
|
Err(anyhow!("delay between messages too long"))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,16 +272,12 @@ impl Peer {
|
||||||
let message_id = incoming.id;
|
let message_id = incoming.id;
|
||||||
tracing::debug!(?incoming, "incoming message future: start");
|
tracing::debug!(?incoming, "incoming message future: start");
|
||||||
let _end = util::defer(move || {
|
let _end = util::defer(move || {
|
||||||
tracing::debug!(
|
tracing::debug!(?connection_id, message_id, "incoming message future: end");
|
||||||
%connection_id,
|
|
||||||
message_id,
|
|
||||||
"incoming message future: end"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(responding_to) = incoming.responding_to {
|
if let Some(responding_to) = incoming.responding_to {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
%connection_id,
|
?connection_id,
|
||||||
message_id,
|
message_id,
|
||||||
responding_to,
|
responding_to,
|
||||||
"incoming response: received"
|
"incoming response: received"
|
||||||
|
@ -274,7 +287,7 @@ impl Peer {
|
||||||
let requester_resumed = oneshot::channel();
|
let requester_resumed = oneshot::channel();
|
||||||
if let Err(error) = tx.send((incoming, requester_resumed.0)) {
|
if let Err(error) = tx.send((incoming, requester_resumed.0)) {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
%connection_id,
|
?connection_id,
|
||||||
message_id,
|
message_id,
|
||||||
responding_to = responding_to,
|
responding_to = responding_to,
|
||||||
?error,
|
?error,
|
||||||
|
@ -283,21 +296,21 @@ impl Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
%connection_id,
|
?connection_id,
|
||||||
message_id,
|
message_id,
|
||||||
responding_to,
|
responding_to,
|
||||||
"incoming response: waiting to resume requester"
|
"incoming response: waiting to resume requester"
|
||||||
);
|
);
|
||||||
let _ = requester_resumed.1.await;
|
let _ = requester_resumed.1.await;
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
%connection_id,
|
?connection_id,
|
||||||
message_id,
|
message_id,
|
||||||
responding_to,
|
responding_to,
|
||||||
"incoming response: requester resumed"
|
"incoming response: requester resumed"
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
%connection_id,
|
?connection_id,
|
||||||
message_id,
|
message_id,
|
||||||
responding_to,
|
responding_to,
|
||||||
"incoming response: unknown request"
|
"incoming response: unknown request"
|
||||||
|
@ -306,14 +319,10 @@ impl Peer {
|
||||||
|
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
tracing::debug!(
|
tracing::debug!(?connection_id, message_id, "incoming message: received");
|
||||||
%connection_id,
|
|
||||||
message_id,
|
|
||||||
"incoming message: received"
|
|
||||||
);
|
|
||||||
proto::build_typed_envelope(connection_id, incoming).or_else(|| {
|
proto::build_typed_envelope(connection_id, incoming).or_else(|| {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
%connection_id,
|
?connection_id,
|
||||||
message_id,
|
message_id,
|
||||||
"unable to construct a typed envelope"
|
"unable to construct a typed envelope"
|
||||||
);
|
);
|
||||||
|
@ -345,6 +354,7 @@ impl Peer {
|
||||||
|
|
||||||
pub fn reset(&self) {
|
pub fn reset(&self) {
|
||||||
self.connections.write().clear();
|
self.connections.write().clear();
|
||||||
|
self.next_connection_id.store(0, SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request<T: RequestMessage>(
|
pub fn request<T: RequestMessage>(
|
||||||
|
@ -384,7 +394,7 @@ impl Peer {
|
||||||
.unbounded_send(proto::Message::Envelope(request.into_envelope(
|
.unbounded_send(proto::Message::Envelope(request.into_envelope(
|
||||||
message_id,
|
message_id,
|
||||||
None,
|
None,
|
||||||
original_sender_id.map(|id| id.0),
|
original_sender_id.map(Into::into),
|
||||||
)))
|
)))
|
||||||
.map_err(|_| anyhow!("connection was closed"))?;
|
.map_err(|_| anyhow!("connection was closed"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -433,7 +443,7 @@ impl Peer {
|
||||||
.unbounded_send(proto::Message::Envelope(message.into_envelope(
|
.unbounded_send(proto::Message::Envelope(message.into_envelope(
|
||||||
message_id,
|
message_id,
|
||||||
None,
|
None,
|
||||||
Some(sender_id.0),
|
Some(sender_id.into()),
|
||||||
)))?;
|
)))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -480,7 +490,7 @@ impl Peer {
|
||||||
let connections = self.connections.read();
|
let connections = self.connections.read();
|
||||||
let connection = connections
|
let connection = connections
|
||||||
.get(&connection_id)
|
.get(&connection_id)
|
||||||
.ok_or_else(|| anyhow!("no such connection: {}", connection_id))?;
|
.ok_or_else(|| anyhow!("no such connection: {:?}", connection_id))?;
|
||||||
Ok(connection.clone())
|
Ok(connection.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -515,9 +525,9 @@ mod tests {
|
||||||
let executor = cx.foreground();
|
let executor = cx.foreground();
|
||||||
|
|
||||||
// create 2 clients connected to 1 server
|
// create 2 clients connected to 1 server
|
||||||
let server = Peer::new();
|
let server = Peer::new(0);
|
||||||
let client1 = Peer::new();
|
let client1 = Peer::new(0);
|
||||||
let client2 = Peer::new();
|
let client2 = Peer::new(0);
|
||||||
|
|
||||||
let (client1_to_server_conn, server_to_client_1_conn, _kill) =
|
let (client1_to_server_conn, server_to_client_1_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(cx.background());
|
||||||
|
@ -609,8 +619,8 @@ mod tests {
|
||||||
#[gpui::test(iterations = 50)]
|
#[gpui::test(iterations = 50)]
|
||||||
async fn test_order_of_response_and_incoming(cx: &mut TestAppContext) {
|
async fn test_order_of_response_and_incoming(cx: &mut TestAppContext) {
|
||||||
let executor = cx.foreground();
|
let executor = cx.foreground();
|
||||||
let server = Peer::new();
|
let server = Peer::new(0);
|
||||||
let client = Peer::new();
|
let client = Peer::new(0);
|
||||||
|
|
||||||
let (client_to_server_conn, server_to_client_conn, _kill) =
|
let (client_to_server_conn, server_to_client_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(cx.background());
|
||||||
|
@ -707,8 +717,8 @@ mod tests {
|
||||||
#[gpui::test(iterations = 50)]
|
#[gpui::test(iterations = 50)]
|
||||||
async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
|
async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
|
||||||
let executor = cx.foreground();
|
let executor = cx.foreground();
|
||||||
let server = Peer::new();
|
let server = Peer::new(0);
|
||||||
let client = Peer::new();
|
let client = Peer::new(0);
|
||||||
|
|
||||||
let (client_to_server_conn, server_to_client_conn, _kill) =
|
let (client_to_server_conn, server_to_client_conn, _kill) =
|
||||||
Connection::in_memory(cx.background());
|
Connection::in_memory(cx.background());
|
||||||
|
@ -822,7 +832,7 @@ mod tests {
|
||||||
|
|
||||||
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
||||||
|
|
||||||
let client = Peer::new();
|
let client = Peer::new(0);
|
||||||
let (connection_id, io_handler, mut incoming) =
|
let (connection_id, io_handler, mut incoming) =
|
||||||
client.add_test_connection(client_conn, cx.background());
|
client.add_test_connection(client_conn, cx.background());
|
||||||
|
|
||||||
|
@ -857,7 +867,7 @@ mod tests {
|
||||||
let executor = cx.foreground();
|
let executor = cx.foreground();
|
||||||
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
let (client_conn, mut server_conn, _kill) = Connection::in_memory(cx.background());
|
||||||
|
|
||||||
let client = Peer::new();
|
let client = Peer::new(0);
|
||||||
let (connection_id, io_handler, mut incoming) =
|
let (connection_id, io_handler, mut incoming) =
|
||||||
client.add_test_connection(client_conn, cx.background());
|
client.add_test_connection(client_conn, cx.background());
|
||||||
executor.spawn(io_handler).detach();
|
executor.spawn(io_handler).detach();
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use super::{entity_messages, messages, request_messages, ConnectionId, PeerId, TypedEnvelope};
|
use super::{entity_messages, messages, request_messages, ConnectionId, TypedEnvelope};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_tungstenite::tungstenite::Message as WebSocketMessage;
|
use async_tungstenite::tungstenite::Message as WebSocketMessage;
|
||||||
use futures::{SinkExt as _, StreamExt as _};
|
use futures::{SinkExt as _, StreamExt as _};
|
||||||
use prost::Message as _;
|
use prost::Message as _;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use std::{cmp, iter, mem};
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::{
|
use std::{
|
||||||
|
cmp,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
io,
|
io, iter, mem,
|
||||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +23,7 @@ pub trait EnvelopedMessage: Clone + Debug + Serialize + Sized + Send + Sync + 's
|
||||||
self,
|
self,
|
||||||
id: u32,
|
id: u32,
|
||||||
responding_to: Option<u32>,
|
responding_to: Option<u32>,
|
||||||
original_sender_id: Option<u32>,
|
original_sender_id: Option<PeerId>,
|
||||||
) -> Envelope;
|
) -> Envelope;
|
||||||
fn from_envelope(envelope: Envelope) -> Option<Self>;
|
fn from_envelope(envelope: Envelope) -> Option<Self>;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +72,67 @@ impl<T: EnvelopedMessage> AnyTypedEnvelope for TypedEnvelope<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn original_sender_id(&self) -> Option<PeerId> {
|
fn original_sender_id(&self) -> Option<PeerId> {
|
||||||
self.original_sender_id
|
self.original_sender_id.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeerId {
|
||||||
|
pub fn from_u64(peer_id: u64) -> Self {
|
||||||
|
let epoch = (peer_id >> 32) as u32;
|
||||||
|
let id = peer_id as u32;
|
||||||
|
Self { epoch, id }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_u64(self) -> u64 {
|
||||||
|
((self.epoch as u64) << 32) | (self.id as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Copy for PeerId {}
|
||||||
|
|
||||||
|
impl Eq for PeerId {}
|
||||||
|
|
||||||
|
impl Ord for PeerId {
|
||||||
|
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||||
|
self.epoch
|
||||||
|
.cmp(&other.epoch)
|
||||||
|
.then_with(|| self.id.cmp(&other.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for PeerId {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for PeerId {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.epoch.hash(state);
|
||||||
|
self.id.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PeerId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}/{}", self.epoch, self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for PeerId {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut components = s.split('/');
|
||||||
|
let epoch = components
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("invalid peer id {:?}", s))?
|
||||||
|
.parse()?;
|
||||||
|
let id = components
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("invalid peer id {:?}", s))?
|
||||||
|
.parse()?;
|
||||||
|
Ok(PeerId { epoch, id })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,4 +539,25 @@ mod tests {
|
||||||
stream.read().await.unwrap();
|
stream.read().await.unwrap();
|
||||||
assert!(stream.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
|
assert!(stream.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_converting_peer_id_from_and_to_u64() {
|
||||||
|
let peer_id = PeerId { epoch: 10, id: 3 };
|
||||||
|
assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
|
||||||
|
let peer_id = PeerId {
|
||||||
|
epoch: u32::MAX,
|
||||||
|
id: 3,
|
||||||
|
};
|
||||||
|
assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
|
||||||
|
let peer_id = PeerId {
|
||||||
|
epoch: 10,
|
||||||
|
id: u32::MAX,
|
||||||
|
};
|
||||||
|
assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
|
||||||
|
let peer_id = PeerId {
|
||||||
|
epoch: u32::MAX,
|
||||||
|
id: u32::MAX,
|
||||||
|
};
|
||||||
|
assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,4 @@ pub use conn::Connection;
|
||||||
pub use peer::*;
|
pub use peer::*;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub const PROTOCOL_VERSION: u32 = 41;
|
pub const PROTOCOL_VERSION: u32 = 42;
|
||||||
|
|
|
@ -280,7 +280,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||||
proto::update_followers::Variant::CreateView(proto::View {
|
proto::update_followers::Variant::CreateView(proto::View {
|
||||||
id: followed_item.id() as u64,
|
id: followed_item.id() as u64,
|
||||||
variant: Some(message),
|
variant: Some(message),
|
||||||
leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
|
leader_id: workspace.leader_for_pane(&pane),
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -334,7 +334,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||||
proto::UpdateView {
|
proto::UpdateView {
|
||||||
id: item.id() as u64,
|
id: item.id() as u64,
|
||||||
variant: pending_update.borrow_mut().take(),
|
variant: pending_update.borrow_mut().take(),
|
||||||
leader_id: leader_id.map(|id| id.0),
|
leader_id,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use call::participant::{Frame, RemoteVideoTrack};
|
use call::participant::{Frame, RemoteVideoTrack};
|
||||||
use client::{PeerId, User};
|
use client::{proto::PeerId, User};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*,
|
elements::*,
|
||||||
|
|
|
@ -25,7 +25,10 @@ use std::{
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use client::{proto, Client, PeerId, TypedEnvelope, UserStore};
|
use client::{
|
||||||
|
proto::{self, PeerId},
|
||||||
|
Client, TypedEnvelope, UserStore,
|
||||||
|
};
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
use dock::{Dock, DockDefaultItemFactory, ToggleDockButton};
|
use dock::{Dock, DockDefaultItemFactory, ToggleDockButton};
|
||||||
use drag_and_drop::DragAndDrop;
|
use drag_and_drop::DragAndDrop;
|
||||||
|
@ -1441,7 +1444,7 @@ impl Workspace {
|
||||||
self.update_followers(
|
self.update_followers(
|
||||||
proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
|
proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
|
||||||
id: self.active_item(cx).map(|item| item.id() as u64),
|
id: self.active_item(cx).map(|item| item.id() as u64),
|
||||||
leader_id: self.leader_for_pane(&pane).map(|id| id.0),
|
leader_id: self.leader_for_pane(&pane),
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -1620,7 +1623,7 @@ impl Workspace {
|
||||||
let project_id = self.project.read(cx).remote_id()?;
|
let project_id = self.project.read(cx).remote_id()?;
|
||||||
let request = self.client.request(proto::Follow {
|
let request = self.client.request(proto::Follow {
|
||||||
project_id,
|
project_id,
|
||||||
leader_id: leader_id.0,
|
leader_id: Some(leader_id),
|
||||||
});
|
});
|
||||||
Some(cx.spawn_weak(|this, mut cx| async move {
|
Some(cx.spawn_weak(|this, mut cx| async move {
|
||||||
let response = request.await?;
|
let response = request.await?;
|
||||||
|
@ -1692,7 +1695,7 @@ impl Workspace {
|
||||||
self.client
|
self.client
|
||||||
.send(proto::Unfollow {
|
.send(proto::Unfollow {
|
||||||
project_id,
|
project_id,
|
||||||
leader_id: leader_id.0,
|
leader_id: Some(leader_id),
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
@ -1888,7 +1891,7 @@ impl Workspace {
|
||||||
.panes()
|
.panes()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|pane| {
|
.flat_map(|pane| {
|
||||||
let leader_id = this.leader_for_pane(pane).map(|id| id.0);
|
let leader_id = this.leader_for_pane(pane);
|
||||||
pane.read(cx).items().filter_map({
|
pane.read(cx).items().filter_map({
|
||||||
let cx = &cx;
|
let cx = &cx;
|
||||||
move |item| {
|
move |item| {
|
||||||
|
@ -1997,7 +2000,7 @@ impl Workspace {
|
||||||
.get(&leader_id)
|
.get(&leader_id)
|
||||||
.map(|c| c.replica_id)
|
.map(|c| c.replica_id)
|
||||||
})
|
})
|
||||||
.ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
|
.ok_or_else(|| anyhow!("no such collaborator {:?}", leader_id))?;
|
||||||
|
|
||||||
let item_builders = cx.update(|cx| {
|
let item_builders = cx.update(|cx| {
|
||||||
cx.default_global::<FollowableItemBuilders>()
|
cx.default_global::<FollowableItemBuilders>()
|
||||||
|
@ -2077,7 +2080,7 @@ impl Workspace {
|
||||||
self.client
|
self.client
|
||||||
.send(proto::UpdateFollowers {
|
.send(proto::UpdateFollowers {
|
||||||
project_id,
|
project_id,
|
||||||
follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
|
follower_ids: self.leader_state.followers.iter().copied().collect(),
|
||||||
variant: Some(update),
|
variant: Some(update),
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
Loading…
Reference in a new issue