Achieve end to end channel buffer synchronization
co-authored-by: max <max@zed.dev>
This commit is contained in:
parent
95ea664725
commit
5a0315c4d5
10 changed files with 425 additions and 128 deletions
|
@ -1,7 +1,14 @@
|
||||||
mod channel_store;
|
mod channel_store;
|
||||||
|
|
||||||
pub mod channel_buffer;
|
pub mod channel_buffer;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use channel_store::*;
|
pub use channel_store::*;
|
||||||
|
use client::Client;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod channel_store_tests;
|
mod channel_store_tests;
|
||||||
|
|
||||||
|
pub fn init(client: &Arc<Client>) {
|
||||||
|
channel_buffer::init(client);
|
||||||
|
}
|
||||||
|
|
|
@ -6,30 +6,34 @@ use rpc::{proto, TypedEnvelope};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
// Open the channel document
|
pub(crate) fn init(client: &Arc<Client>) {
|
||||||
// ChannelDocumentView { ChannelDocument, Editor } -> On clone, clones internal ChannelDocument handle, instantiates new editor
|
client.add_model_message_handler(ChannelBuffer::handle_update_channel_buffer);
|
||||||
// Produces a view which is: (ChannelDocument, Editor), ChannelDocument manages subscriptions
|
client.add_model_message_handler(ChannelBuffer::handle_add_channel_buffer_collaborator);
|
||||||
// ChannelDocuments -> Buffers -> Editor with that buffer
|
client.add_model_message_handler(ChannelBuffer::handle_remove_channel_buffer_collaborator);
|
||||||
|
}
|
||||||
// ChannelDocuments {
|
|
||||||
// ChannleBuffers: HashMap<bufferId, ModelHandle<language::Buffer>>
|
|
||||||
// }
|
|
||||||
|
|
||||||
type BufferId = u64;
|
|
||||||
|
|
||||||
pub struct ChannelBuffer {
|
pub struct ChannelBuffer {
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
buffer_id: BufferId,
|
collaborators: Vec<proto::Collaborator>,
|
||||||
buffer: ModelHandle<language::Buffer>,
|
buffer: ModelHandle<language::Buffer>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
|
_subscription: client::Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for ChannelBuffer {
|
impl Entity for ChannelBuffer {
|
||||||
type Event = ();
|
type Event = ();
|
||||||
|
|
||||||
|
fn release(&mut self, _: &mut AppContext) {
|
||||||
|
self.client
|
||||||
|
.send(proto::LeaveChannelBuffer {
|
||||||
|
channel_id: self.channel_id,
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelBuffer {
|
impl ChannelBuffer {
|
||||||
pub fn for_channel(
|
pub fn join_channel(
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
|
@ -45,19 +49,24 @@ impl ChannelBuffer {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(language::proto::deserialize_operation)
|
.map(language::proto::deserialize_operation)
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
let buffer_id = response.buffer_id;
|
|
||||||
|
|
||||||
let buffer = cx.add_model(|cx| language::Buffer::new(0, base_text, cx));
|
let collaborators = response.collaborators;
|
||||||
|
|
||||||
|
let buffer =
|
||||||
|
cx.add_model(|cx| language::Buffer::new(response.replica_id as u16, base_text, cx));
|
||||||
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
|
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
|
||||||
|
|
||||||
|
let subscription = client.subscribe_to_entity(channel_id)?;
|
||||||
|
|
||||||
anyhow::Ok(cx.add_model(|cx| {
|
anyhow::Ok(cx.add_model(|cx| {
|
||||||
cx.subscribe(&buffer, Self::on_buffer_update).detach();
|
cx.subscribe(&buffer, Self::on_buffer_update).detach();
|
||||||
client.add_model_message_handler(Self::handle_update_channel_buffer);
|
|
||||||
Self {
|
Self {
|
||||||
buffer_id,
|
|
||||||
buffer,
|
buffer,
|
||||||
client,
|
client,
|
||||||
channel_id,
|
channel_id,
|
||||||
|
collaborators,
|
||||||
|
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
@ -77,6 +86,7 @@ impl ChannelBuffer {
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
|
cx.notify();
|
||||||
this.buffer
|
this.buffer
|
||||||
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
|
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
|
||||||
})?;
|
})?;
|
||||||
|
@ -84,6 +94,49 @@ impl ChannelBuffer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_add_channel_buffer_collaborator(
|
||||||
|
this: ModelHandle<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::AddChannelBufferCollaborator>,
|
||||||
|
_: Arc<Client>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let collaborator = envelope.payload.collaborator.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Should have gotten a collaborator in the AddChannelBufferCollaborator message"
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.collaborators.push(collaborator);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_remove_channel_buffer_collaborator(
|
||||||
|
this: ModelHandle<Self>,
|
||||||
|
message: TypedEnvelope<proto::RemoveChannelBufferCollaborator>,
|
||||||
|
_: Arc<Client>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.collaborators.retain(|collaborator| {
|
||||||
|
if collaborator.peer_id == message.payload.peer_id {
|
||||||
|
this.buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.remove_peer(collaborator.replica_id as u16, cx)
|
||||||
|
});
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn on_buffer_update(
|
fn on_buffer_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: ModelHandle<language::Buffer>,
|
_: ModelHandle<language::Buffer>,
|
||||||
|
@ -94,7 +147,7 @@ impl ChannelBuffer {
|
||||||
let operation = language::proto::serialize_operation(operation);
|
let operation = language::proto::serialize_operation(operation);
|
||||||
self.client
|
self.client
|
||||||
.send(proto::UpdateChannelBuffer {
|
.send(proto::UpdateChannelBuffer {
|
||||||
buffer_id: self.buffer_id,
|
channel_id: self.channel_id,
|
||||||
operations: vec![operation],
|
operations: vec![operation],
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
@ -104,4 +157,8 @@ impl ChannelBuffer {
|
||||||
pub fn buffer(&self) -> ModelHandle<language::Buffer> {
|
pub fn buffer(&self) -> ModelHandle<language::Buffer> {
|
||||||
self.buffer.clone()
|
self.buffer.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn collaborators(&self) -> &[proto::Collaborator] {
|
||||||
|
&self.collaborators
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ impl Database {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
let tx = tx;
|
let tx = tx;
|
||||||
|
|
||||||
// Get or create buffer from channel
|
|
||||||
self.check_user_is_channel_member(channel_id, user_id, &tx)
|
self.check_user_is_channel_member(channel_id, user_id, &tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -116,6 +115,7 @@ impl Database {
|
||||||
|
|
||||||
Ok(proto::JoinChannelBufferResponse {
|
Ok(proto::JoinChannelBufferResponse {
|
||||||
buffer_id: buffer.id.to_proto(),
|
buffer_id: buffer.id.to_proto(),
|
||||||
|
replica_id: replica_id.to_proto() as u32,
|
||||||
base_text,
|
base_text,
|
||||||
operations,
|
operations,
|
||||||
collaborators: collaborators
|
collaborators: collaborators
|
||||||
|
@ -137,67 +137,128 @@ impl Database {
|
||||||
connection: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<Vec<ConnectionId>> {
|
) -> Result<Vec<ConnectionId>> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
let result = channel_buffer_collaborator::Entity::delete_many()
|
self.leave_channel_buffer_internal(channel_id, connection, &*tx)
|
||||||
.filter(
|
.await
|
||||||
Condition::all()
|
|
||||||
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
|
|
||||||
.add(
|
|
||||||
channel_buffer_collaborator::Column::ConnectionId
|
|
||||||
.eq(connection.id as i32),
|
|
||||||
)
|
|
||||||
.add(
|
|
||||||
channel_buffer_collaborator::Column::ConnectionServerId
|
|
||||||
.eq(connection.owner_id as i32),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.exec(&*tx)
|
|
||||||
.await?;
|
|
||||||
if result.rows_affected == 0 {
|
|
||||||
Err(anyhow!("not a collaborator on this project"))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut connections = Vec::new();
|
|
||||||
let mut rows = channel_buffer_collaborator::Entity::find()
|
|
||||||
.filter(
|
|
||||||
Condition::all()
|
|
||||||
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
|
|
||||||
)
|
|
||||||
.stream(&*tx)
|
|
||||||
.await?;
|
|
||||||
while let Some(row) = rows.next().await {
|
|
||||||
let row = row?;
|
|
||||||
connections.push(ConnectionId {
|
|
||||||
id: row.connection_id as u32,
|
|
||||||
owner_id: row.connection_server_id.0 as u32,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(connections)
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn leave_channel_buffer_internal(
|
||||||
|
&self,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
connection: ConnectionId,
|
||||||
|
tx: &DatabaseTransaction,
|
||||||
|
) -> Result<Vec<ConnectionId>> {
|
||||||
|
let result = channel_buffer_collaborator::Entity::delete_many()
|
||||||
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
|
||||||
|
.add(channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32))
|
||||||
|
.add(
|
||||||
|
channel_buffer_collaborator::Column::ConnectionServerId
|
||||||
|
.eq(connection.owner_id as i32),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.exec(&*tx)
|
||||||
|
.await?;
|
||||||
|
if result.rows_affected == 0 {
|
||||||
|
Err(anyhow!("not a collaborator on this project"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut connections = Vec::new();
|
||||||
|
let mut rows = channel_buffer_collaborator::Entity::find()
|
||||||
|
.filter(
|
||||||
|
Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
|
||||||
|
)
|
||||||
|
.stream(&*tx)
|
||||||
|
.await?;
|
||||||
|
while let Some(row) = rows.next().await {
|
||||||
|
let row = row?;
|
||||||
|
connections.push(ConnectionId {
|
||||||
|
id: row.connection_id as u32,
|
||||||
|
owner_id: row.connection_server_id.0 as u32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(connections)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn leave_channel_buffers(
|
pub async fn leave_channel_buffers(
|
||||||
&self,
|
&self,
|
||||||
connection: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<Option<LeftChannelBuffers>> {
|
) -> Result<Vec<(ChannelId, Vec<ConnectionId>)>> {
|
||||||
//
|
self.transaction(|tx| async move {
|
||||||
|
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
|
||||||
|
enum QueryChannelIds {
|
||||||
|
ChannelId,
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel_ids: Vec<ChannelId> = channel_buffer_collaborator::Entity::find()
|
||||||
|
.select_only()
|
||||||
|
.column(channel_buffer_collaborator::Column::ChannelId)
|
||||||
|
.filter(Condition::all().add(
|
||||||
|
channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32),
|
||||||
|
))
|
||||||
|
.into_values::<_, QueryChannelIds>()
|
||||||
|
.all(&*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for channel_id in channel_ids {
|
||||||
|
let collaborators = self
|
||||||
|
.leave_channel_buffer_internal(channel_id, connection, &*tx)
|
||||||
|
.await?;
|
||||||
|
result.push((channel_id, collaborators));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_channel_buffer_collaborators(&self, channel_id: ChannelId) -> Result<()> {
|
#[cfg(debug_assertions)]
|
||||||
todo!()
|
pub async fn get_channel_buffer_collaborators(
|
||||||
|
&self,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
) -> Result<Vec<UserId>> {
|
||||||
|
self.transaction(|tx| async move {
|
||||||
|
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
|
||||||
|
enum QueryUserIds {
|
||||||
|
UserId,
|
||||||
|
}
|
||||||
|
|
||||||
|
let users: Vec<UserId> = channel_buffer_collaborator::Entity::find()
|
||||||
|
.select_only()
|
||||||
|
.column(channel_buffer_collaborator::Column::UserId)
|
||||||
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
|
||||||
|
)
|
||||||
|
.into_values::<_, QueryUserIds>()
|
||||||
|
.all(&*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(users)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_channel_buffer(
|
pub async fn update_channel_buffer(
|
||||||
&self,
|
&self,
|
||||||
buffer_id: BufferId,
|
channel_id: ChannelId,
|
||||||
|
user: UserId,
|
||||||
operations: &[proto::Operation],
|
operations: &[proto::Operation],
|
||||||
) -> Result<()> {
|
) -> Result<Vec<ConnectionId>> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
let buffer = buffer::Entity::find_by_id(buffer_id)
|
self.check_user_is_channel_member(channel_id, user, &*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let buffer = buffer::Entity::find()
|
||||||
|
.filter(buffer::Column::ChannelId.eq(channel_id))
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("no such buffer"))?;
|
.ok_or_else(|| anyhow!("no such buffer"))?;
|
||||||
|
let buffer_id = buffer.id;
|
||||||
buffer_operation::Entity::insert_many(operations.iter().filter_map(|operation| {
|
buffer_operation::Entity::insert_many(operations.iter().filter_map(|operation| {
|
||||||
match operation.variant.as_ref()? {
|
match operation.variant.as_ref()? {
|
||||||
proto::operation::Variant::Edit(operation) => {
|
proto::operation::Variant::Edit(operation) => {
|
||||||
|
@ -237,7 +298,23 @@ impl Database {
|
||||||
.exec(&*tx)
|
.exec(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
let mut connections = Vec::new();
|
||||||
|
let mut rows = channel_buffer_collaborator::Entity::find()
|
||||||
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
|
||||||
|
)
|
||||||
|
.stream(&*tx)
|
||||||
|
.await?;
|
||||||
|
while let Some(row) = rows.next().await {
|
||||||
|
let row = row?;
|
||||||
|
connections.push(ConnectionId {
|
||||||
|
id: row.connection_id as u32,
|
||||||
|
owner_id: row.connection_server_id.0 as u32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(connections)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,11 +66,10 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let connection_id_a = ConnectionId { owner_id, id: 1 };
|
let connection_id_a = ConnectionId { owner_id, id: 1 };
|
||||||
let buffer_response_a = db
|
let _ = db
|
||||||
.join_channel_buffer(zed_id, a_id, connection_id_a)
|
.join_channel_buffer(zed_id, a_id, connection_id_a)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let buffer_id = BufferId::from_proto(buffer_response_a.buffer_id);
|
|
||||||
|
|
||||||
let mut buffer_a = Buffer::new(0, 0, "".to_string());
|
let mut buffer_a = Buffer::new(0, 0, "".to_string());
|
||||||
let mut operations = Vec::new();
|
let mut operations = Vec::new();
|
||||||
|
@ -85,7 +84,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||||
.map(|op| proto::serialize_operation(&language::Operation::Buffer(op)))
|
.map(|op| proto::serialize_operation(&language::Operation::Buffer(op)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
db.update_channel_buffer(buffer_id, &operations)
|
db.update_channel_buffer(zed_id, a_id, &operations)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -115,7 +114,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||||
.await
|
.await
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
//Ensure that both collaborators have shown up
|
// Ensure that both collaborators have shown up
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer_response_b.collaborators,
|
buffer_response_b.collaborators,
|
||||||
&[
|
&[
|
||||||
|
@ -132,6 +131,10 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Ensure that get_channel_buffer_collaborators works
|
||||||
|
let zed_collaborats = db.get_channel_buffer_collaborators(zed_id).await.unwrap();
|
||||||
|
assert_eq!(zed_collaborats, &[a_id, b_id]);
|
||||||
|
|
||||||
let collaborators = db
|
let collaborators = db
|
||||||
.leave_channel_buffer(zed_id, connection_id_b)
|
.leave_channel_buffer(zed_id, connection_id_b)
|
||||||
.await
|
.await
|
||||||
|
@ -139,7 +142,18 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||||
|
|
||||||
assert_eq!(collaborators, &[connection_id_a],);
|
assert_eq!(collaborators, &[connection_id_a],);
|
||||||
|
|
||||||
db.connection_lost(connection_id_a).await.unwrap();
|
let cargo_id = db.create_root_channel("cargo", "2", a_id).await.unwrap();
|
||||||
// assert!()
|
let _ = db
|
||||||
// Test buffer epoch incrementing?
|
.join_channel_buffer(cargo_id, a_id, connection_id_a)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.leave_channel_buffers(connection_id_a).await.unwrap();
|
||||||
|
|
||||||
|
let zed_collaborators = db.get_channel_buffer_collaborators(zed_id).await.unwrap();
|
||||||
|
let cargo_collaborators = db.get_channel_buffer_collaborators(cargo_id).await.unwrap();
|
||||||
|
assert_eq!(zed_collaborators, &[]);
|
||||||
|
assert_eq!(cargo_collaborators, &[]);
|
||||||
|
|
||||||
|
// TODO: test buffer epoch incrementing
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@ mod connection_pool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth,
|
auth,
|
||||||
db::{
|
db::{self, ChannelId, ChannelsForUser, Database, ProjectId, RoomId, ServerId, User, UserId},
|
||||||
self, BufferId, ChannelId, ChannelsForUser, Database, ProjectId, RoomId, ServerId, User,
|
|
||||||
UserId,
|
|
||||||
},
|
|
||||||
executor::Executor,
|
executor::Executor,
|
||||||
AppState, Result,
|
AppState, Result,
|
||||||
};
|
};
|
||||||
|
@ -38,8 +35,8 @@ use lazy_static::lazy_static;
|
||||||
use prometheus::{register_int_gauge, IntGauge};
|
use prometheus::{register_int_gauge, IntGauge};
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{
|
proto::{
|
||||||
self, Ack, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo,
|
self, Ack, AddChannelBufferCollaborator, AnyTypedEnvelope, EntityMessage, EnvelopedMessage,
|
||||||
RequestMessage,
|
LiveKitConnectionInfo, RequestMessage,
|
||||||
},
|
},
|
||||||
Connection, ConnectionId, Peer, Receipt, TypedEnvelope,
|
Connection, ConnectionId, Peer, Receipt, TypedEnvelope,
|
||||||
};
|
};
|
||||||
|
@ -860,6 +857,7 @@ async fn connection_lost(
|
||||||
futures::select_biased! {
|
futures::select_biased! {
|
||||||
_ = executor.sleep(RECONNECT_TIMEOUT).fuse() => {
|
_ = executor.sleep(RECONNECT_TIMEOUT).fuse() => {
|
||||||
leave_room_for_session(&session).await.trace_err();
|
leave_room_for_session(&session).await.trace_err();
|
||||||
|
leave_channel_buffers_for_session(&session).await.trace_err();
|
||||||
|
|
||||||
if !session
|
if !session
|
||||||
.connection_pool()
|
.connection_pool()
|
||||||
|
@ -872,6 +870,8 @@ async fn connection_lost(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update_user_contacts(session.user_id, &session).await?;
|
update_user_contacts(session.user_id, &session).await?;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
_ = teardown.changed().fuse() => {}
|
_ = teardown.changed().fuse() => {}
|
||||||
}
|
}
|
||||||
|
@ -2496,8 +2496,51 @@ async fn join_channel_buffer(
|
||||||
.join_channel_buffer(channel_id, session.user_id, session.connection_id)
|
.join_channel_buffer(channel_id, session.user_id, session.connection_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let replica_id = open_response.replica_id;
|
||||||
|
let collaborators = open_response.collaborators.clone();
|
||||||
|
|
||||||
response.send(open_response)?;
|
response.send(open_response)?;
|
||||||
|
|
||||||
|
let update = AddChannelBufferCollaborator {
|
||||||
|
channel_id: channel_id.to_proto(),
|
||||||
|
collaborator: Some(proto::Collaborator {
|
||||||
|
user_id: session.user_id.to_proto(),
|
||||||
|
peer_id: Some(session.connection_id.into()),
|
||||||
|
replica_id,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
channel_buffer_updated(
|
||||||
|
session.connection_id,
|
||||||
|
collaborators
|
||||||
|
.iter()
|
||||||
|
.filter_map(|collaborator| Some(collaborator.peer_id?.into())),
|
||||||
|
&update,
|
||||||
|
&session.peer,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_channel_buffer(
|
||||||
|
request: proto::UpdateChannelBuffer,
|
||||||
|
session: Session,
|
||||||
|
) -> Result<()> {
|
||||||
|
let db = session.db().await;
|
||||||
|
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||||
|
|
||||||
|
let collaborators = db
|
||||||
|
.update_channel_buffer(channel_id, session.user_id, &request.operations)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
channel_buffer_updated(
|
||||||
|
session.connection_id,
|
||||||
|
collaborators,
|
||||||
|
&proto::UpdateChannelBuffer {
|
||||||
|
channel_id: channel_id.to_proto(),
|
||||||
|
operations: request.operations,
|
||||||
|
},
|
||||||
|
&session.peer,
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2515,18 +2558,28 @@ async fn leave_channel_buffer(
|
||||||
|
|
||||||
response.send(Ack {})?;
|
response.send(Ack {})?;
|
||||||
|
|
||||||
|
channel_buffer_updated(
|
||||||
|
session.connection_id,
|
||||||
|
collaborators_to_notify,
|
||||||
|
&proto::RemoveChannelBufferCollaborator {
|
||||||
|
channel_id: channel_id.to_proto(),
|
||||||
|
peer_id: Some(session.connection_id.into()),
|
||||||
|
},
|
||||||
|
&session.peer,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_channel_buffer(
|
fn channel_buffer_updated<T: EnvelopedMessage>(
|
||||||
request: proto::UpdateChannelBuffer,
|
sender_id: ConnectionId,
|
||||||
session: Session,
|
collaborators: impl IntoIterator<Item = ConnectionId>,
|
||||||
) -> Result<()> {
|
message: &T,
|
||||||
let db = session.db().await;
|
peer: &Peer,
|
||||||
|
) {
|
||||||
// TODO: Broadcast to buffer members
|
broadcast(Some(sender_id), collaborators.into_iter(), |peer_id| {
|
||||||
|
peer.send(peer_id.into(), message.clone())
|
||||||
Ok(())
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_diff_base(request: proto::UpdateDiffBase, session: Session) -> Result<()> {
|
async fn update_diff_base(request: proto::UpdateDiffBase, session: Session) -> Result<()> {
|
||||||
|
@ -2854,6 +2907,28 @@ async fn leave_room_for_session(session: &Session) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn leave_channel_buffers_for_session(session: &Session) -> Result<()> {
|
||||||
|
let left_channel_buffers = session
|
||||||
|
.db()
|
||||||
|
.await
|
||||||
|
.leave_channel_buffers(session.connection_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for (channel_id, connections) in left_channel_buffers {
|
||||||
|
channel_buffer_updated(
|
||||||
|
session.connection_id,
|
||||||
|
connections,
|
||||||
|
&proto::RemoveChannelBufferCollaborator {
|
||||||
|
channel_id: channel_id.to_proto(),
|
||||||
|
peer_id: Some(session.connection_id.into()),
|
||||||
|
},
|
||||||
|
&session.peer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn project_left(project: &db::LeftProject, session: &Session) {
|
fn project_left(project: &db::LeftProject, session: &Session) {
|
||||||
for connection_id in &project.connection_ids {
|
for connection_id in &project.connection_ids {
|
||||||
if project.host_user_id == session.user_id {
|
if project.host_user_id == session.user_id {
|
||||||
|
|
|
@ -211,6 +211,7 @@ impl TestServer {
|
||||||
workspace::init(app_state.clone(), cx);
|
workspace::init(app_state.clone(), cx);
|
||||||
audio::init((), cx);
|
audio::init((), cx);
|
||||||
call::init(client.clone(), user_store.clone(), cx);
|
call::init(client.clone(), user_store.clone(), cx);
|
||||||
|
channel::init(&client);
|
||||||
});
|
});
|
||||||
|
|
||||||
client
|
client
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use crate::tests::TestServer;
|
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
||||||
|
|
||||||
use channel::channel_buffer::ChannelBuffer;
|
use channel::channel_buffer::ChannelBuffer;
|
||||||
|
use client::UserId;
|
||||||
use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
|
use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
|
||||||
use std::{ops::Range, sync::Arc};
|
use rpc::{proto, RECEIVE_TIMEOUT};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_channel_buffers(
|
async fn test_core_channel_buffers(
|
||||||
deterministic: Arc<Deterministic>,
|
deterministic: Arc<Deterministic>,
|
||||||
cx_a: &mut TestAppContext,
|
cx_a: &mut TestAppContext,
|
||||||
cx_b: &mut TestAppContext,
|
cx_b: &mut TestAppContext,
|
||||||
|
@ -19,60 +21,103 @@ async fn test_channel_buffers(
|
||||||
.make_channel("zed", (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
.make_channel("zed", (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
// Client A joins the channel buffer
|
||||||
let channel_buffer_a = cx_a
|
let channel_buffer_a = cx_a
|
||||||
.update(|cx| ChannelBuffer::for_channel(zed_id, client_a.client().to_owned(), cx))
|
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// Client A edits the buffer
|
||||||
let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
|
let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
|
||||||
|
|
||||||
edit_channel_buffer(&buffer_a, cx_a, [(0..0, "hello world")]);
|
buffer_a.update(cx_a, |buffer, cx| {
|
||||||
edit_channel_buffer(&buffer_a, cx_a, [(5..5, ", cruel")]);
|
buffer.edit([(0..0, "hello world")], None, cx)
|
||||||
edit_channel_buffer(&buffer_a, cx_a, [(0..5, "goodbye")]);
|
});
|
||||||
undo_channel_buffer(&buffer_a, cx_a);
|
buffer_a.update(cx_a, |buffer, cx| {
|
||||||
|
buffer.edit([(5..5, ", cruel")], None, cx)
|
||||||
|
});
|
||||||
|
buffer_a.update(cx_a, |buffer, cx| {
|
||||||
|
buffer.edit([(0..5, "goodbye")], None, cx)
|
||||||
|
});
|
||||||
|
buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
|
||||||
assert_eq!(channel_buffer_text(&buffer_a, cx_a), "hello, cruel world");
|
assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
|
||||||
|
|
||||||
|
// Client B joins the channel buffer
|
||||||
let channel_buffer_b = cx_b
|
let channel_buffer_b = cx_b
|
||||||
.update(|cx| ChannelBuffer::for_channel(zed_id, client_b.client().to_owned(), cx))
|
.update(|cx| ChannelBuffer::join_channel(zed_id, client_b.client().to_owned(), cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
channel_buffer_b.read_with(cx_b, |buffer, _| {
|
||||||
|
assert_collaborators(
|
||||||
|
buffer.collaborators(),
|
||||||
|
&[client_a.user_id(), client_b.user_id()],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Client B sees the correct text, and then edits it
|
||||||
let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
|
let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
|
||||||
|
assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
|
||||||
|
buffer_b.update(cx_b, |buffer, cx| {
|
||||||
|
buffer.edit([(7..12, "beautiful")], None, cx)
|
||||||
|
});
|
||||||
|
|
||||||
assert_eq!(channel_buffer_text(&buffer_b, cx_b), "hello, cruel world");
|
// Both A and B see the new edit
|
||||||
|
deterministic.run_until_parked();
|
||||||
edit_channel_buffer(&buffer_b, cx_b, [(7..12, "beautiful")]);
|
assert_eq!(buffer_text(&buffer_a, cx_a), "hello, beautiful world");
|
||||||
|
assert_eq!(buffer_text(&buffer_b, cx_b), "hello, beautiful world");
|
||||||
|
|
||||||
|
// Client A closes the channel buffer.
|
||||||
|
cx_a.update(|_| drop(channel_buffer_a));
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
|
|
||||||
|
// Client B sees that client A is gone from the channel buffer.
|
||||||
|
channel_buffer_b.read_with(cx_b, |buffer, _| {
|
||||||
|
assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Client A rejoins the channel buffer
|
||||||
|
let _channel_buffer_a = cx_a
|
||||||
|
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
|
||||||
|
// Sanity test, make sure we saw A rejoining
|
||||||
|
channel_buffer_b.read_with(cx_b, |buffer, _| {
|
||||||
|
assert_collaborators(
|
||||||
|
&buffer.collaborators(),
|
||||||
|
&[client_b.user_id(), client_a.user_id()],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Client A loses connection.
|
||||||
|
server.forbid_connections();
|
||||||
|
server.disconnect_client(client_a.peer_id().unwrap());
|
||||||
|
deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
|
||||||
|
|
||||||
|
// Client B observes A disconnect
|
||||||
|
channel_buffer_b.read_with(cx_b, |buffer, _| {
|
||||||
|
assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Test synchronizing offline updates, what happens to A's channel buffer?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_collaborators(collaborators: &[proto::Collaborator], ids: &[Option<UserId>]) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
channel_buffer_text(&buffer_a, cx_a),
|
collaborators
|
||||||
"hello, beautiful world"
|
.into_iter()
|
||||||
);
|
.map(|collaborator| collaborator.user_id)
|
||||||
assert_eq!(
|
.collect::<Vec<_>>(),
|
||||||
channel_buffer_text(&buffer_b, cx_b),
|
ids.into_iter().map(|id| id.unwrap()).collect::<Vec<_>>()
|
||||||
"hello, beautiful world"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit_channel_buffer<I>(
|
fn buffer_text(channel_buffer: &ModelHandle<language::Buffer>, cx: &mut TestAppContext) -> String {
|
||||||
channel_buffer: &ModelHandle<language::Buffer>,
|
|
||||||
cx: &mut TestAppContext,
|
|
||||||
edits: I,
|
|
||||||
) where
|
|
||||||
I: IntoIterator<Item = (Range<usize>, &'static str)>,
|
|
||||||
{
|
|
||||||
channel_buffer.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn undo_channel_buffer(channel_buffer: &ModelHandle<language::Buffer>, cx: &mut TestAppContext) {
|
|
||||||
channel_buffer.update(cx, |buffer, cx| buffer.undo(cx));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn channel_buffer_text(
|
|
||||||
channel_buffer: &ModelHandle<language::Buffer>,
|
|
||||||
cx: &mut TestAppContext,
|
|
||||||
) -> String {
|
|
||||||
channel_buffer.read_with(cx, |buffer, _| buffer.text())
|
channel_buffer.read_with(cx, |buffer, _| buffer.text())
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,6 +147,8 @@ message Envelope {
|
||||||
JoinChannelBufferResponse join_channel_buffer_response = 132;
|
JoinChannelBufferResponse join_channel_buffer_response = 132;
|
||||||
UpdateChannelBuffer update_channel_buffer = 133;
|
UpdateChannelBuffer update_channel_buffer = 133;
|
||||||
LeaveChannelBuffer leave_channel_buffer = 134;
|
LeaveChannelBuffer leave_channel_buffer = 134;
|
||||||
|
AddChannelBufferCollaborator add_channel_buffer_collaborator = 135;
|
||||||
|
RemoveChannelBufferCollaborator remove_channel_buffer_collaborator = 136;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,6 +418,16 @@ message RemoveProjectCollaborator {
|
||||||
PeerId peer_id = 2;
|
PeerId peer_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message AddChannelBufferCollaborator {
|
||||||
|
uint64 channel_id = 1;
|
||||||
|
Collaborator collaborator = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveChannelBufferCollaborator {
|
||||||
|
uint64 channel_id = 1;
|
||||||
|
PeerId peer_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message GetDefinition {
|
message GetDefinition {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 buffer_id = 2;
|
uint64 buffer_id = 2;
|
||||||
|
@ -546,8 +558,8 @@ message UpdateBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateChannelBuffer {
|
message UpdateChannelBuffer {
|
||||||
uint64 buffer_id = 2;
|
uint64 channel_id = 1;
|
||||||
repeated Operation operations = 3;
|
repeated Operation operations = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateBufferFile {
|
message UpdateBufferFile {
|
||||||
|
@ -964,9 +976,10 @@ message JoinChannelBuffer {
|
||||||
|
|
||||||
message JoinChannelBufferResponse {
|
message JoinChannelBufferResponse {
|
||||||
uint64 buffer_id = 1;
|
uint64 buffer_id = 1;
|
||||||
string base_text = 2;
|
uint32 replica_id = 2;
|
||||||
repeated Operation operations = 3;
|
string base_text = 3;
|
||||||
repeated Collaborator collaborators = 4;
|
repeated Operation operations = 4;
|
||||||
|
repeated Collaborator collaborators = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LeaveChannelBuffer {
|
message LeaveChannelBuffer {
|
||||||
|
|
|
@ -252,7 +252,9 @@ messages!(
|
||||||
(JoinChannelBuffer, Foreground),
|
(JoinChannelBuffer, Foreground),
|
||||||
(JoinChannelBufferResponse, Foreground),
|
(JoinChannelBufferResponse, Foreground),
|
||||||
(LeaveChannelBuffer, Background),
|
(LeaveChannelBuffer, Background),
|
||||||
(UpdateChannelBuffer, Foreground)
|
(UpdateChannelBuffer, Foreground),
|
||||||
|
(RemoveChannelBufferCollaborator, Foreground),
|
||||||
|
(AddChannelBufferCollaborator, Foreground),
|
||||||
);
|
);
|
||||||
|
|
||||||
request_messages!(
|
request_messages!(
|
||||||
|
@ -376,7 +378,12 @@ entity_messages!(
|
||||||
UpdateDiffBase
|
UpdateDiffBase
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_messages!(buffer_id, UpdateChannelBuffer);
|
entity_messages!(
|
||||||
|
channel_id,
|
||||||
|
UpdateChannelBuffer,
|
||||||
|
RemoveChannelBufferCollaborator,
|
||||||
|
AddChannelBufferCollaborator
|
||||||
|
);
|
||||||
|
|
||||||
const KIB: usize = 1024;
|
const KIB: usize = 1024;
|
||||||
const MIB: usize = KIB * 1024;
|
const MIB: usize = KIB * 1024;
|
||||||
|
|
|
@ -158,6 +158,7 @@ fn main() {
|
||||||
outline::init(cx);
|
outline::init(cx);
|
||||||
project_symbols::init(cx);
|
project_symbols::init(cx);
|
||||||
project_panel::init(Assets, cx);
|
project_panel::init(Assets, cx);
|
||||||
|
channel::init(&client);
|
||||||
diagnostics::init(cx);
|
diagnostics::init(cx);
|
||||||
search::init(cx);
|
search::init(cx);
|
||||||
semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
|
semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue