Channel notifications from the server works
This commit is contained in:
parent
1469c02998
commit
9ba975d6ad
16 changed files with 266 additions and 107 deletions
|
@ -43,6 +43,7 @@ pub type ChannelData = (Channel, ChannelPath);
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
pub id: ChannelId,
|
pub id: ChannelId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub has_changed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
|
||||||
|
@ -207,6 +208,13 @@ impl ChannelStore {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_channel_buffer_changed(&self, channel_id: ChannelId) -> Option<bool> {
|
||||||
|
self.channel_index
|
||||||
|
.by_id()
|
||||||
|
.get(&channel_id)
|
||||||
|
.map(|channel| channel.has_changed)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open_channel_chat(
|
pub fn open_channel_chat(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
|
@ -779,6 +787,7 @@ impl ChannelStore {
|
||||||
Arc::new(Channel {
|
Arc::new(Channel {
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
name: channel.name,
|
name: channel.name,
|
||||||
|
has_changed: false,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -787,7 +796,8 @@ impl ChannelStore {
|
||||||
let channels_changed = !payload.channels.is_empty()
|
let channels_changed = !payload.channels.is_empty()
|
||||||
|| !payload.delete_channels.is_empty()
|
|| !payload.delete_channels.is_empty()
|
||||||
|| !payload.insert_edge.is_empty()
|
|| !payload.insert_edge.is_empty()
|
||||||
|| !payload.delete_edge.is_empty();
|
|| !payload.delete_edge.is_empty()
|
||||||
|
|| !payload.notes_changed.is_empty();
|
||||||
|
|
||||||
if channels_changed {
|
if channels_changed {
|
||||||
if !payload.delete_channels.is_empty() {
|
if !payload.delete_channels.is_empty() {
|
||||||
|
@ -814,6 +824,10 @@ impl ChannelStore {
|
||||||
index.insert(channel)
|
index.insert(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for id_changed in payload.notes_changed {
|
||||||
|
index.has_changed(id_changed);
|
||||||
|
}
|
||||||
|
|
||||||
for edge in payload.insert_edge {
|
for edge in payload.insert_edge {
|
||||||
index.insert_edge(edge.channel_id, edge.parent_id);
|
index.insert_edge(edge.channel_id, edge.parent_id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,12 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_changed(&mut self, channel_id: ChannelId) {
|
||||||
|
if let Some(channel) = self.channels_by_id.get_mut(&channel_id) {
|
||||||
|
Arc::make_mut(channel).has_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, channel_proto: proto::Channel) {
|
pub fn insert(&mut self, channel_proto: proto::Channel) {
|
||||||
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
|
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
|
||||||
Arc::make_mut(existing_channel).name = channel_proto.name;
|
Arc::make_mut(existing_channel).name = channel_proto.name;
|
||||||
|
@ -85,6 +91,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
||||||
Arc::new(Channel {
|
Arc::new(Channel {
|
||||||
id: channel_proto.id,
|
id: channel_proto.id,
|
||||||
name: channel_proto.name,
|
name: channel_proto.name,
|
||||||
|
has_changed: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
self.insert_root(channel_proto.id);
|
self.insert_root(channel_proto.id);
|
||||||
|
|
|
@ -291,12 +291,12 @@ CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
|
||||||
CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");
|
CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE "observed_channel_note_edits" (
|
CREATE TABLE "observed_buffer_edits" (
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
||||||
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
|
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
|
||||||
"epoch" INTEGER NOT NULL,
|
"epoch" INTEGER NOT NULL,
|
||||||
"lamport_timestamp" INTEGER NOT NULL,
|
"lamport_timestamp" INTEGER NOT NULL,
|
||||||
PRIMARY KEY (user_id, channel_id)
|
PRIMARY KEY (user_id, buffer_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_observed_notes_user_and_channel_id" ON "observed_channel_note_edits" ("user_id", "channel_id");
|
CREATE UNIQUE INDEX "index_observed_buffers_user_and_buffer_id" ON "observed_buffer_edits" ("user_id", "buffer_id");
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
CREATE TABLE "observed_channel_note_edits" (
|
CREATE TABLE "observed_buffer_edits" (
|
||||||
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
||||||
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
|
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
|
||||||
"epoch" INTEGER NOT NULL,
|
"epoch" INTEGER NOT NULL,
|
||||||
"lamport_timestamp" INTEGER NOT NULL,
|
"lamport_timestamp" INTEGER NOT NULL,
|
||||||
PRIMARY KEY (user_id, channel_id)
|
PRIMARY KEY (user_id, buffer_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "index_observed_notes_user_and_channel_id" ON "observed_channel_note_edits" ("user_id", "channel_id");
|
CREATE UNIQUE INDEX "index_observed_buffer_user_and_buffer_id" ON "observed_buffer_edits" ("user_id", "buffer_id");
|
||||||
|
|
|
@ -436,6 +436,7 @@ pub struct Channel {
|
||||||
pub struct ChannelsForUser {
|
pub struct ChannelsForUser {
|
||||||
pub channels: ChannelGraph,
|
pub channels: ChannelGraph,
|
||||||
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
|
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
|
||||||
|
pub channels_with_changed_notes: HashSet<ChannelId>,
|
||||||
pub channels_with_admin_privileges: HashSet<ChannelId>,
|
pub channels_with_admin_privileges: HashSet<ChannelId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,20 +80,20 @@ impl Database {
|
||||||
|
|
||||||
// Save the last observed operation
|
// Save the last observed operation
|
||||||
if let Some(max_operation) = max_operation {
|
if let Some(max_operation) = max_operation {
|
||||||
observed_note_edits::Entity::insert(observed_note_edits::ActiveModel {
|
observed_buffer_edits::Entity::insert(observed_buffer_edits::ActiveModel {
|
||||||
user_id: ActiveValue::Set(user_id),
|
user_id: ActiveValue::Set(user_id),
|
||||||
channel_id: ActiveValue::Set(channel_id),
|
buffer_id: ActiveValue::Set(buffer.id),
|
||||||
epoch: ActiveValue::Set(max_operation.0),
|
epoch: ActiveValue::Set(max_operation.0),
|
||||||
lamport_timestamp: ActiveValue::Set(max_operation.1),
|
lamport_timestamp: ActiveValue::Set(max_operation.1),
|
||||||
})
|
})
|
||||||
.on_conflict(
|
.on_conflict(
|
||||||
OnConflict::columns([
|
OnConflict::columns([
|
||||||
observed_note_edits::Column::UserId,
|
observed_buffer_edits::Column::UserId,
|
||||||
observed_note_edits::Column::ChannelId,
|
observed_buffer_edits::Column::BufferId,
|
||||||
])
|
])
|
||||||
.update_columns([
|
.update_columns([
|
||||||
observed_note_edits::Column::Epoch,
|
observed_buffer_edits::Column::Epoch,
|
||||||
observed_note_edits::Column::LamportTimestamp,
|
observed_buffer_edits::Column::LamportTimestamp,
|
||||||
])
|
])
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
|
@ -110,20 +110,20 @@ impl Database {
|
||||||
.map(|model| (model.epoch, model.lamport_timestamp));
|
.map(|model| (model.epoch, model.lamport_timestamp));
|
||||||
|
|
||||||
if let Some(buffer_max) = buffer_max {
|
if let Some(buffer_max) = buffer_max {
|
||||||
observed_note_edits::Entity::insert(observed_note_edits::ActiveModel {
|
observed_buffer_edits::Entity::insert(observed_buffer_edits::ActiveModel {
|
||||||
user_id: ActiveValue::Set(user_id),
|
user_id: ActiveValue::Set(user_id),
|
||||||
channel_id: ActiveValue::Set(channel_id),
|
buffer_id: ActiveValue::Set(buffer.id),
|
||||||
epoch: ActiveValue::Set(buffer_max.0),
|
epoch: ActiveValue::Set(buffer_max.0),
|
||||||
lamport_timestamp: ActiveValue::Set(buffer_max.1),
|
lamport_timestamp: ActiveValue::Set(buffer_max.1),
|
||||||
})
|
})
|
||||||
.on_conflict(
|
.on_conflict(
|
||||||
OnConflict::columns([
|
OnConflict::columns([
|
||||||
observed_note_edits::Column::UserId,
|
observed_buffer_edits::Column::UserId,
|
||||||
observed_note_edits::Column::ChannelId,
|
observed_buffer_edits::Column::BufferId,
|
||||||
])
|
])
|
||||||
.update_columns([
|
.update_columns([
|
||||||
observed_note_edits::Column::Epoch,
|
observed_buffer_edits::Column::Epoch,
|
||||||
observed_note_edits::Column::LamportTimestamp,
|
observed_buffer_edits::Column::LamportTimestamp,
|
||||||
])
|
])
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
|
@ -463,7 +463,7 @@ impl Database {
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
user: UserId,
|
user: UserId,
|
||||||
operations: &[proto::Operation],
|
operations: &[proto::Operation],
|
||||||
) -> Result<Vec<ConnectionId>> {
|
) -> Result<(Vec<ConnectionId>, Vec<UserId>)> {
|
||||||
self.transaction(move |tx| async move {
|
self.transaction(move |tx| async move {
|
||||||
self.check_user_is_channel_member(channel_id, user, &*tx)
|
self.check_user_is_channel_member(channel_id, user, &*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -483,10 +483,23 @@ impl Database {
|
||||||
.filter_map(|op| operation_to_storage(op, &buffer, serialization_version))
|
.filter_map(|op| operation_to_storage(op, &buffer, serialization_version))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut channel_members;
|
||||||
|
|
||||||
if !operations.is_empty() {
|
if !operations.is_empty() {
|
||||||
// get current channel participants and save the max operation above
|
// get current channel participants and save the max operation above
|
||||||
self.save_max_operation_for_collaborators(operations.as_slice(), channel_id, &*tx)
|
self.save_max_operation_for_collaborators(
|
||||||
|
operations.as_slice(),
|
||||||
|
channel_id,
|
||||||
|
buffer.id,
|
||||||
|
&*tx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
channel_members = self.get_channel_members_internal(channel_id, &*tx).await?;
|
||||||
|
let collaborators = self
|
||||||
|
.get_channel_buffer_collaborators_internal(channel_id, &*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
channel_members.retain(|member| !collaborators.contains(member));
|
||||||
|
|
||||||
buffer_operation::Entity::insert_many(operations)
|
buffer_operation::Entity::insert_many(operations)
|
||||||
.on_conflict(
|
.on_conflict(
|
||||||
|
@ -501,6 +514,8 @@ impl Database {
|
||||||
)
|
)
|
||||||
.exec(&*tx)
|
.exec(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
} else {
|
||||||
|
channel_members = Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut connections = Vec::new();
|
let mut connections = Vec::new();
|
||||||
|
@ -519,7 +534,7 @@ impl Database {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(connections)
|
Ok((connections, channel_members))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -528,6 +543,7 @@ impl Database {
|
||||||
&self,
|
&self,
|
||||||
operations: &[buffer_operation::ActiveModel],
|
operations: &[buffer_operation::ActiveModel],
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
|
buffer_id: BufferId,
|
||||||
tx: &DatabaseTransaction,
|
tx: &DatabaseTransaction,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let max_operation = operations
|
let max_operation = operations
|
||||||
|
@ -553,22 +569,22 @@ impl Database {
|
||||||
.get_channel_buffer_collaborators_internal(channel_id, tx)
|
.get_channel_buffer_collaborators_internal(channel_id, tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
observed_note_edits::Entity::insert_many(users.iter().map(|id| {
|
observed_buffer_edits::Entity::insert_many(users.iter().map(|id| {
|
||||||
observed_note_edits::ActiveModel {
|
observed_buffer_edits::ActiveModel {
|
||||||
user_id: ActiveValue::Set(*id),
|
user_id: ActiveValue::Set(*id),
|
||||||
channel_id: ActiveValue::Set(channel_id),
|
buffer_id: ActiveValue::Set(buffer_id),
|
||||||
epoch: max_operation.0.clone(),
|
epoch: max_operation.0.clone(),
|
||||||
lamport_timestamp: ActiveValue::Set(*max_operation.1.as_ref()),
|
lamport_timestamp: ActiveValue::Set(*max_operation.1.as_ref()),
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.on_conflict(
|
.on_conflict(
|
||||||
OnConflict::columns([
|
OnConflict::columns([
|
||||||
observed_note_edits::Column::UserId,
|
observed_buffer_edits::Column::UserId,
|
||||||
observed_note_edits::Column::ChannelId,
|
observed_buffer_edits::Column::BufferId,
|
||||||
])
|
])
|
||||||
.update_columns([
|
.update_columns([
|
||||||
observed_note_edits::Column::Epoch,
|
observed_buffer_edits::Column::Epoch,
|
||||||
observed_note_edits::Column::LamportTimestamp,
|
observed_buffer_edits::Column::LamportTimestamp,
|
||||||
])
|
])
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
|
@ -699,54 +715,75 @@ impl Database {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn has_buffer_changed(&self, user_id: UserId, channel_id: ChannelId) -> Result<bool> {
|
#[cfg(test)]
|
||||||
self.transaction(|tx| async move {
|
pub async fn test_has_note_changed(
|
||||||
let user_max = observed_note_edits::Entity::find()
|
&self,
|
||||||
.filter(observed_note_edits::Column::UserId.eq(user_id))
|
user_id: UserId,
|
||||||
.filter(observed_note_edits::Column::ChannelId.eq(channel_id))
|
channel_id: ChannelId,
|
||||||
.one(&*tx)
|
) -> Result<bool> {
|
||||||
.await?
|
self.transaction(|tx| async move { self.has_note_changed(user_id, channel_id, &*tx).await })
|
||||||
.map(|model| (model.epoch, model.lamport_timestamp));
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
let channel_buffer = channel::Model {
|
pub async fn has_note_changed(
|
||||||
id: channel_id,
|
&self,
|
||||||
..Default::default()
|
user_id: UserId,
|
||||||
}
|
channel_id: ChannelId,
|
||||||
.find_related(buffer::Entity)
|
tx: &DatabaseTransaction,
|
||||||
|
) -> Result<bool> {
|
||||||
|
let Some(buffer_id) = channel::Model {
|
||||||
|
id: channel_id,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.find_related(buffer::Entity)
|
||||||
|
.one(&*tx)
|
||||||
|
.await?
|
||||||
|
.map(|buffer| buffer.id) else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_max = observed_buffer_edits::Entity::find()
|
||||||
|
.filter(observed_buffer_edits::Column::UserId.eq(user_id))
|
||||||
|
.filter(observed_buffer_edits::Column::BufferId.eq(buffer_id))
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?;
|
.await?
|
||||||
|
.map(|model| (model.epoch, model.lamport_timestamp));
|
||||||
|
|
||||||
let Some(channel_buffer) = channel_buffer else {
|
let channel_buffer = channel::Model {
|
||||||
return Ok(false);
|
id: channel_id,
|
||||||
};
|
..Default::default()
|
||||||
|
}
|
||||||
|
.find_related(buffer::Entity)
|
||||||
|
.one(&*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut channel_max = buffer_operation::Entity::find()
|
let Some(channel_buffer) = channel_buffer else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut channel_max = buffer_operation::Entity::find()
|
||||||
|
.filter(buffer_operation::Column::BufferId.eq(channel_buffer.id))
|
||||||
|
.filter(buffer_operation::Column::Epoch.eq(channel_buffer.epoch))
|
||||||
|
.order_by(buffer_operation::Column::Epoch, Desc)
|
||||||
|
.order_by(buffer_operation::Column::LamportTimestamp, Desc)
|
||||||
|
.one(&*tx)
|
||||||
|
.await?
|
||||||
|
.map(|model| (model.epoch, model.lamport_timestamp));
|
||||||
|
|
||||||
|
// If there are no edits in this epoch
|
||||||
|
if channel_max.is_none() {
|
||||||
|
// check if this user observed the last edit of the previous epoch
|
||||||
|
channel_max = buffer_operation::Entity::find()
|
||||||
.filter(buffer_operation::Column::BufferId.eq(channel_buffer.id))
|
.filter(buffer_operation::Column::BufferId.eq(channel_buffer.id))
|
||||||
.filter(buffer_operation::Column::Epoch.eq(channel_buffer.epoch))
|
.filter(buffer_operation::Column::Epoch.eq(channel_buffer.epoch.saturating_sub(1)))
|
||||||
.order_by(buffer_operation::Column::Epoch, Desc)
|
.order_by(buffer_operation::Column::Epoch, Desc)
|
||||||
.order_by(buffer_operation::Column::LamportTimestamp, Desc)
|
.order_by(buffer_operation::Column::LamportTimestamp, Desc)
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.map(|model| (model.epoch, model.lamport_timestamp));
|
.map(|model| (model.epoch, model.lamport_timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
// If there are no edits in this epoch
|
Ok(user_max != channel_max)
|
||||||
if channel_max.is_none() {
|
|
||||||
// check if this user observed the last edit of the previous epoch
|
|
||||||
channel_max = buffer_operation::Entity::find()
|
|
||||||
.filter(buffer_operation::Column::BufferId.eq(channel_buffer.id))
|
|
||||||
.filter(
|
|
||||||
buffer_operation::Column::Epoch.eq(channel_buffer.epoch.saturating_sub(1)),
|
|
||||||
)
|
|
||||||
.order_by(buffer_operation::Column::Epoch, Desc)
|
|
||||||
.order_by(buffer_operation::Column::LamportTimestamp, Desc)
|
|
||||||
.one(&*tx)
|
|
||||||
.await?
|
|
||||||
.map(|model| (model.epoch, model.lamport_timestamp));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(user_max != channel_max)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -391,7 +391,8 @@ impl Database {
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.get_user_channels(channel_memberships, &tx).await
|
self.get_user_channels(user_id, channel_memberships, &tx)
|
||||||
|
.await
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -414,13 +415,15 @@ impl Database {
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.get_user_channels(channel_membership, &tx).await
|
self.get_user_channels(user_id, channel_membership, &tx)
|
||||||
|
.await
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_user_channels(
|
pub async fn get_user_channels(
|
||||||
&self,
|
&self,
|
||||||
|
user_id: UserId,
|
||||||
channel_memberships: Vec<channel_member::Model>,
|
channel_memberships: Vec<channel_member::Model>,
|
||||||
tx: &DatabaseTransaction,
|
tx: &DatabaseTransaction,
|
||||||
) -> Result<ChannelsForUser> {
|
) -> Result<ChannelsForUser> {
|
||||||
|
@ -460,10 +463,18 @@ impl Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut channels_with_changed_notes = HashSet::default();
|
||||||
|
for channel in graph.channels.iter() {
|
||||||
|
if self.has_note_changed(user_id, channel.id, tx).await? {
|
||||||
|
channels_with_changed_notes.insert(channel.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ChannelsForUser {
|
Ok(ChannelsForUser {
|
||||||
channels: graph,
|
channels: graph,
|
||||||
channel_participants,
|
channel_participants,
|
||||||
channels_with_admin_privileges,
|
channels_with_admin_privileges,
|
||||||
|
channels_with_changed_notes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub mod contact;
|
||||||
pub mod feature_flag;
|
pub mod feature_flag;
|
||||||
pub mod follower;
|
pub mod follower;
|
||||||
pub mod language_server;
|
pub mod language_server;
|
||||||
pub mod observed_note_edits;
|
pub mod observed_buffer_edits;
|
||||||
pub mod project;
|
pub mod project;
|
||||||
pub mod project_collaborator;
|
pub mod project_collaborator;
|
||||||
pub mod room;
|
pub mod room;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::db::{ChannelId, UserId};
|
use crate::db::{BufferId, UserId};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
#[sea_orm(table_name = "observed_channel_note_edits")]
|
#[sea_orm(table_name = "observed_buffer_edits")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub channel_id: ChannelId,
|
pub buffer_id: BufferId,
|
||||||
pub epoch: i32,
|
pub epoch: i32,
|
||||||
pub lamport_timestamp: i32,
|
pub lamport_timestamp: i32,
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,11 @@ pub struct Model {
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {
|
pub enum Relation {
|
||||||
#[sea_orm(
|
#[sea_orm(
|
||||||
belongs_to = "super::channel::Entity",
|
belongs_to = "super::buffer::Entity",
|
||||||
from = "Column::ChannelId",
|
from = "Column::BufferId",
|
||||||
to = "super::channel::Column::Id"
|
to = "super::buffer::Column::Id"
|
||||||
)]
|
)]
|
||||||
Channel,
|
Buffer,
|
||||||
#[sea_orm(
|
#[sea_orm(
|
||||||
belongs_to = "super::user::Entity",
|
belongs_to = "super::user::Entity",
|
||||||
from = "Column::UserId",
|
from = "Column::UserId",
|
||||||
|
@ -27,9 +27,9 @@ pub enum Relation {
|
||||||
User,
|
User,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Related<super::channel::Entity> for Entity {
|
impl Related<super::buffer::Entity> for Entity {
|
||||||
fn to() -> RelationDef {
|
fn to() -> RelationDef {
|
||||||
Relation::Channel.def()
|
Relation::Buffer.def()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,7 +220,7 @@ async fn test_channel_buffers_diffs(db: &Database) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zero test: A should not register as changed on an unitialized channel buffer
|
// Zero test: A should not register as changed on an unitialized channel buffer
|
||||||
assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
|
assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
|
||||||
|
|
||||||
let _ = db
|
let _ = db
|
||||||
.join_channel_buffer(zed_id, a_id, connection_id_a)
|
.join_channel_buffer(zed_id, a_id, connection_id_a)
|
||||||
|
@ -228,7 +228,7 @@ async fn test_channel_buffers_diffs(db: &Database) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Zero test: A should register as changed on an empty channel buffer
|
// Zero test: A should register as changed on an empty channel buffer
|
||||||
assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
|
assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
|
||||||
|
|
||||||
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();
|
||||||
|
@ -245,15 +245,16 @@ async fn test_channel_buffers_diffs(db: &Database) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Smoke test: Does B register as changed, A as unchanged?
|
// Smoke test: Does B register as changed, A as unchanged?
|
||||||
assert!(db.has_buffer_changed(b_id, zed_id).await.unwrap());
|
assert!(db.test_has_note_changed(b_id, zed_id).await.unwrap());
|
||||||
assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
|
|
||||||
|
assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
|
||||||
|
|
||||||
db.leave_channel_buffer(zed_id, connection_id_a)
|
db.leave_channel_buffer(zed_id, connection_id_a)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Snapshotting from leaving the channel buffer should not have a diff
|
// Snapshotting from leaving the channel buffer should not have a diff
|
||||||
assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
|
assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
|
||||||
|
|
||||||
let _ = db
|
let _ = db
|
||||||
.join_channel_buffer(zed_id, b_id, connection_id_b)
|
.join_channel_buffer(zed_id, b_id, connection_id_b)
|
||||||
|
@ -261,13 +262,13 @@ async fn test_channel_buffers_diffs(db: &Database) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// B has opened the channel buffer, so we shouldn't have any diff
|
// B has opened the channel buffer, so we shouldn't have any diff
|
||||||
assert!(!db.has_buffer_changed(b_id, zed_id).await.unwrap());
|
assert!(!db.test_has_note_changed(b_id, zed_id).await.unwrap());
|
||||||
|
|
||||||
db.leave_channel_buffer(zed_id, connection_id_b)
|
db.leave_channel_buffer(zed_id, connection_id_b)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Since B just opened and closed the buffer without editing, neither should have a diff
|
// Since B just opened and closed the buffer without editing, neither should have a diff
|
||||||
assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
|
assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
|
||||||
assert!(!db.has_buffer_changed(b_id, zed_id).await.unwrap());
|
assert!(!db.test_has_note_changed(b_id, zed_id).await.unwrap());
|
||||||
}
|
}
|
||||||
|
|
|
@ -2691,7 +2691,7 @@ async fn update_channel_buffer(
|
||||||
let db = session.db().await;
|
let db = session.db().await;
|
||||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||||
|
|
||||||
let collaborators = db
|
let (collaborators, non_collaborators) = db
|
||||||
.update_channel_buffer(channel_id, session.user_id, &request.operations)
|
.update_channel_buffer(channel_id, session.user_id, &request.operations)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -2704,6 +2704,25 @@ async fn update_channel_buffer(
|
||||||
},
|
},
|
||||||
&session.peer,
|
&session.peer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let pool = &*session.connection_pool().await;
|
||||||
|
|
||||||
|
broadcast(
|
||||||
|
None,
|
||||||
|
non_collaborators
|
||||||
|
.iter()
|
||||||
|
.flat_map(|user_id| pool.user_connection_ids(*user_id)),
|
||||||
|
|peer_id| {
|
||||||
|
session.peer.send(
|
||||||
|
peer_id.into(),
|
||||||
|
proto::UpdateChannels {
|
||||||
|
notes_changed: vec![channel_id.to_proto()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2986,6 +3005,12 @@ fn build_initial_channels_update(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update.notes_changed = channels
|
||||||
|
.channels_with_changed_notes
|
||||||
|
.iter()
|
||||||
|
.map(|channel_id| channel_id.to_proto())
|
||||||
|
.collect();
|
||||||
|
|
||||||
update.insert_edge = channels.channels.edges;
|
update.insert_edge = channels.channels.edges;
|
||||||
|
|
||||||
for (channel_id, participants) in channels.channel_participants {
|
for (channel_id, participants) in channels.channel_participants {
|
||||||
|
|
|
@ -410,10 +410,7 @@ async fn test_channel_buffer_disconnect(
|
||||||
channel_buffer_a.update(cx_a, |buffer, _| {
|
channel_buffer_a.update(cx_a, |buffer, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer.channel().as_ref(),
|
buffer.channel().as_ref(),
|
||||||
&Channel {
|
&channel(channel_id, "the-channel")
|
||||||
id: channel_id,
|
|
||||||
name: "the-channel".to_string()
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
assert!(!buffer.is_connected());
|
assert!(!buffer.is_connected());
|
||||||
});
|
});
|
||||||
|
@ -438,15 +435,20 @@ async fn test_channel_buffer_disconnect(
|
||||||
channel_buffer_b.update(cx_b, |buffer, _| {
|
channel_buffer_b.update(cx_b, |buffer, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer.channel().as_ref(),
|
buffer.channel().as_ref(),
|
||||||
&Channel {
|
&channel(channel_id, "the-channel")
|
||||||
id: channel_id,
|
|
||||||
name: "the-channel".to_string()
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
assert!(!buffer.is_connected());
|
assert!(!buffer.is_connected());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn channel(id: u64, name: &'static str) -> Channel {
|
||||||
|
Channel {
|
||||||
|
id,
|
||||||
|
name: name.to_string(),
|
||||||
|
has_changed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_rejoin_channel_buffer(
|
async fn test_rejoin_channel_buffer(
|
||||||
deterministic: Arc<Deterministic>,
|
deterministic: Arc<Deterministic>,
|
||||||
|
@ -627,6 +629,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||||
let mut server = TestServer::start(&deterministic).await;
|
let mut server = TestServer::start(&deterministic).await;
|
||||||
let client_a = server.create_client(cx_a, "user_a").await;
|
let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
let client_b = server.create_client(cx_b, "user_b").await;
|
let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
|
|
||||||
let client_c = server.create_client(cx_c, "user_c").await;
|
let client_c = server.create_client(cx_c, "user_c").await;
|
||||||
|
|
||||||
cx_a.update(editor::init);
|
cx_a.update(editor::init);
|
||||||
|
@ -757,6 +760,50 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_channel_buffer_changes(
|
||||||
|
deterministic: Arc<Deterministic>,
|
||||||
|
cx_a: &mut TestAppContext,
|
||||||
|
cx_b: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
deterministic.forbid_parking();
|
||||||
|
let mut server = TestServer::start(&deterministic).await;
|
||||||
|
let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
|
let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
|
|
||||||
|
let channel_id = server
|
||||||
|
.make_channel(
|
||||||
|
"the-channel",
|
||||||
|
None,
|
||||||
|
(&client_a, cx_a),
|
||||||
|
&mut [(&client_b, cx_b)],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let channel_buffer_a = client_a
|
||||||
|
.channel_store()
|
||||||
|
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||||
|
buffer.buffer().update(cx, |buffer, cx| {
|
||||||
|
buffer.edit([(0..0, "1")], None, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
|
||||||
|
let has_buffer_changed = cx_b.read(|cx| {
|
||||||
|
client_b
|
||||||
|
.channel_store()
|
||||||
|
.read(cx)
|
||||||
|
.has_channel_buffer_changed(channel_id)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(has_buffer_changed);
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Option<UserId>]) {
|
fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Option<UserId>]) {
|
||||||
let mut user_ids = collaborators
|
let mut user_ids = collaborators
|
||||||
|
|
|
@ -1816,12 +1816,19 @@ impl CollabPanel {
|
||||||
.left(),
|
.left(),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
Label::new(channel.name.clone(), theme.channel_name.text.clone())
|
Label::new(
|
||||||
.contained()
|
channel.name.clone(),
|
||||||
.with_style(theme.channel_name.container)
|
theme
|
||||||
.aligned()
|
.channel_name
|
||||||
.left()
|
.in_state(channel.has_changed)
|
||||||
.flex(1., true),
|
.text
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
.contained()
|
||||||
|
.with_style(theme.channel_name.container)
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
.flex(1., true),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |_, cx| {
|
MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |_, cx| {
|
||||||
|
|
|
@ -955,6 +955,7 @@ message UpdateChannels {
|
||||||
repeated uint64 remove_channel_invitations = 6;
|
repeated uint64 remove_channel_invitations = 6;
|
||||||
repeated ChannelParticipants channel_participants = 7;
|
repeated ChannelParticipants channel_participants = 7;
|
||||||
repeated ChannelPermission channel_permissions = 8;
|
repeated ChannelPermission channel_permissions = 8;
|
||||||
|
repeated uint64 notes_changed = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ChannelEdge {
|
message ChannelEdge {
|
||||||
|
|
|
@ -251,7 +251,7 @@ pub struct CollabPanel {
|
||||||
pub leave_call: Interactive<ContainedText>,
|
pub leave_call: Interactive<ContainedText>,
|
||||||
pub contact_row: Toggleable<Interactive<ContainerStyle>>,
|
pub contact_row: Toggleable<Interactive<ContainerStyle>>,
|
||||||
pub channel_row: Toggleable<Interactive<ContainerStyle>>,
|
pub channel_row: Toggleable<Interactive<ContainerStyle>>,
|
||||||
pub channel_name: ContainedText,
|
pub channel_name: Toggleable<ContainedText>,
|
||||||
pub row_height: f32,
|
pub row_height: f32,
|
||||||
pub project_row: Toggleable<Interactive<ProjectRow>>,
|
pub project_row: Toggleable<Interactive<ProjectRow>>,
|
||||||
pub tree_branch: Toggleable<Interactive<TreeBranch>>,
|
pub tree_branch: Toggleable<Interactive<TreeBranch>>,
|
||||||
|
|
|
@ -267,10 +267,18 @@ export default function contacts_panel(): any {
|
||||||
}),
|
}),
|
||||||
channel_row: item_row,
|
channel_row: item_row,
|
||||||
channel_name: {
|
channel_name: {
|
||||||
...text(layer, "sans", { size: "sm" }),
|
active: {
|
||||||
margin: {
|
...text(layer, "sans", { size: "sm", weight: "bold" }),
|
||||||
left: CHANNEL_SPACING,
|
margin: {
|
||||||
|
left: CHANNEL_SPACING,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
inactive: {
|
||||||
|
...text(layer, "sans", { size: "sm" }),
|
||||||
|
margin: {
|
||||||
|
left: CHANNEL_SPACING,
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
list_empty_label_container: {
|
list_empty_label_container: {
|
||||||
margin: {
|
margin: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue