Simplify Membership Management (#6747)
Simplify Zed's collaboration system by: - Only allowing member management on root channels. - Disallowing moving sub-channels between different roots. - Disallowing public channels nested under private channels. This should make the mental model easier to understand, and makes it clearer who has what access. It is also significantly simpler to implement, and so hopefully more performant and less buggy. Still TODO: - [x] Update collab_ui to match. - [x] Fix channel buffer tests. Release Notes: - Simplified channel membership management.
This commit is contained in:
commit
8bc105ca1d
31 changed files with 900 additions and 1441 deletions
|
@ -61,11 +61,12 @@ impl ChannelBuffer {
|
||||||
.map(language::proto::deserialize_operation)
|
.map(language::proto::deserialize_operation)
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let buffer = cx.new_model(|_| {
|
let buffer = cx.new_model(|cx| {
|
||||||
|
let capability = channel_store.read(cx).channel_capability(channel.id);
|
||||||
language::Buffer::remote(
|
language::Buffer::remote(
|
||||||
response.buffer_id,
|
response.buffer_id,
|
||||||
response.replica_id as u16,
|
response.replica_id as u16,
|
||||||
channel.channel_buffer_capability(),
|
capability,
|
||||||
base_text,
|
base_text,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -13,11 +13,11 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::Capability;
|
use language::Capability;
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, ChannelVisibility},
|
proto::{self, ChannelRole, ChannelVisibility},
|
||||||
TypedEnvelope,
|
TypedEnvelope,
|
||||||
};
|
};
|
||||||
use std::{mem, sync::Arc, time::Duration};
|
use std::{mem, sync::Arc, time::Duration};
|
||||||
use util::{async_maybe, ResultExt};
|
use util::{async_maybe, maybe, ResultExt};
|
||||||
|
|
||||||
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||||
let channel_store =
|
let channel_store =
|
||||||
|
@ -29,33 +29,47 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
pub type ChannelId = u64;
|
pub type ChannelId = u64;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct NotesVersion {
|
||||||
|
epoch: u64,
|
||||||
|
version: clock::Global,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ChannelStore {
|
pub struct ChannelStore {
|
||||||
pub channel_index: ChannelIndex,
|
pub channel_index: ChannelIndex,
|
||||||
channel_invitations: Vec<Arc<Channel>>,
|
channel_invitations: Vec<Arc<Channel>>,
|
||||||
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
|
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
|
||||||
|
channel_states: HashMap<ChannelId, ChannelState>,
|
||||||
|
|
||||||
outgoing_invites: HashSet<(ChannelId, UserId)>,
|
outgoing_invites: HashSet<(ChannelId, UserId)>,
|
||||||
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
|
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
|
||||||
opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
|
opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
|
||||||
opened_chats: HashMap<ChannelId, OpenedModelHandle<ChannelChat>>,
|
opened_chats: HashMap<ChannelId, OpenedModelHandle<ChannelChat>>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: Model<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
_rpc_subscription: Subscription,
|
_rpc_subscriptions: [Subscription; 2],
|
||||||
_watch_connection_status: Task<Option<()>>,
|
_watch_connection_status: Task<Option<()>>,
|
||||||
disconnect_channel_buffers_task: Option<Task<()>>,
|
disconnect_channel_buffers_task: Option<Task<()>>,
|
||||||
_update_channels: Task<()>,
|
_update_channels: Task<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
pub id: ChannelId,
|
pub id: ChannelId,
|
||||||
pub name: SharedString,
|
pub name: SharedString,
|
||||||
pub visibility: proto::ChannelVisibility,
|
pub visibility: proto::ChannelVisibility,
|
||||||
pub role: proto::ChannelRole,
|
|
||||||
pub unseen_note_version: Option<(u64, clock::Global)>,
|
|
||||||
pub unseen_message_id: Option<u64>,
|
|
||||||
pub parent_path: Vec<u64>,
|
pub parent_path: Vec<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ChannelState {
|
||||||
|
latest_chat_message: Option<u64>,
|
||||||
|
latest_notes_versions: Option<NotesVersion>,
|
||||||
|
observed_chat_message: Option<u64>,
|
||||||
|
observed_notes_versions: Option<NotesVersion>,
|
||||||
|
role: Option<ChannelRole>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Channel {
|
impl Channel {
|
||||||
pub fn link(&self) -> String {
|
pub fn link(&self) -> String {
|
||||||
RELEASE_CHANNEL.link_prefix().to_owned()
|
RELEASE_CHANNEL.link_prefix().to_owned()
|
||||||
|
@ -65,6 +79,17 @@ impl Channel {
|
||||||
+ &self.id.to_string()
|
+ &self.id.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_root_channel(&self) -> bool {
|
||||||
|
self.parent_path.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root_id(&self) -> ChannelId {
|
||||||
|
self.parent_path
|
||||||
|
.first()
|
||||||
|
.map(|id| *id as ChannelId)
|
||||||
|
.unwrap_or(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn slug(&self) -> String {
|
pub fn slug(&self) -> String {
|
||||||
let slug: String = self
|
let slug: String = self
|
||||||
.name
|
.name
|
||||||
|
@ -74,14 +99,6 @@ impl Channel {
|
||||||
|
|
||||||
slug.trim_matches(|c| c == '-').to_string()
|
slug.trim_matches(|c| c == '-').to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel_buffer_capability(&self) -> Capability {
|
|
||||||
if self.role == proto::ChannelRole::Member || self.role == proto::ChannelRole::Admin {
|
|
||||||
Capability::ReadWrite
|
|
||||||
} else {
|
|
||||||
Capability::ReadOnly
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChannelMembership {
|
pub struct ChannelMembership {
|
||||||
|
@ -100,8 +117,7 @@ impl ChannelMembership {
|
||||||
},
|
},
|
||||||
kind_order: match self.kind {
|
kind_order: match self.kind {
|
||||||
proto::channel_member::Kind::Member => 0,
|
proto::channel_member::Kind::Member => 0,
|
||||||
proto::channel_member::Kind::AncestorMember => 1,
|
proto::channel_member::Kind::Invitee => 1,
|
||||||
proto::channel_member::Kind::Invitee => 2,
|
|
||||||
},
|
},
|
||||||
username_order: self.user.github_login.as_str(),
|
username_order: self.user.github_login.as_str(),
|
||||||
}
|
}
|
||||||
|
@ -137,8 +153,10 @@ impl ChannelStore {
|
||||||
user_store: Model<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rpc_subscription =
|
let rpc_subscriptions = [
|
||||||
client.add_message_handler(cx.weak_model(), Self::handle_update_channels);
|
client.add_message_handler(cx.weak_model(), Self::handle_update_channels),
|
||||||
|
client.add_message_handler(cx.weak_model(), Self::handle_update_user_channels),
|
||||||
|
];
|
||||||
|
|
||||||
let mut connection_status = client.status();
|
let mut connection_status = client.status();
|
||||||
let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
|
let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
|
||||||
|
@ -175,7 +193,7 @@ impl ChannelStore {
|
||||||
update_channels_tx,
|
update_channels_tx,
|
||||||
client,
|
client,
|
||||||
user_store,
|
user_store,
|
||||||
_rpc_subscription: rpc_subscription,
|
_rpc_subscriptions: rpc_subscriptions,
|
||||||
_watch_connection_status: watch_connection_status,
|
_watch_connection_status: watch_connection_status,
|
||||||
disconnect_channel_buffers_task: None,
|
disconnect_channel_buffers_task: None,
|
||||||
_update_channels: cx.spawn(|this, mut cx| async move {
|
_update_channels: cx.spawn(|this, mut cx| async move {
|
||||||
|
@ -195,6 +213,7 @@ impl ChannelStore {
|
||||||
.await
|
.await
|
||||||
.log_err();
|
.log_err();
|
||||||
}),
|
}),
|
||||||
|
channel_states: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,39 +325,16 @@ impl ChannelStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_channel_buffer_changed(&self, channel_id: ChannelId) -> Option<bool> {
|
pub fn has_channel_buffer_changed(&self, channel_id: ChannelId) -> bool {
|
||||||
self.channel_index
|
self.channel_states
|
||||||
.by_id()
|
|
||||||
.get(&channel_id)
|
.get(&channel_id)
|
||||||
.map(|channel| channel.unseen_note_version.is_some())
|
.is_some_and(|state| state.has_channel_buffer_changed())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_new_messages(&self, channel_id: ChannelId) -> Option<bool> {
|
pub fn has_new_messages(&self, channel_id: ChannelId) -> bool {
|
||||||
self.channel_index
|
self.channel_states
|
||||||
.by_id()
|
|
||||||
.get(&channel_id)
|
.get(&channel_id)
|
||||||
.map(|channel| channel.unseen_message_id.is_some())
|
.is_some_and(|state| state.has_new_messages())
|
||||||
}
|
|
||||||
|
|
||||||
pub fn notes_changed(
|
|
||||||
&mut self,
|
|
||||||
channel_id: ChannelId,
|
|
||||||
epoch: u64,
|
|
||||||
version: &clock::Global,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) {
|
|
||||||
self.channel_index.note_changed(channel_id, epoch, version);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_message(
|
|
||||||
&mut self,
|
|
||||||
channel_id: ChannelId,
|
|
||||||
message_id: u64,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) {
|
|
||||||
self.channel_index.new_message(channel_id, message_id);
|
|
||||||
cx.notify();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn acknowledge_message_id(
|
pub fn acknowledge_message_id(
|
||||||
|
@ -347,8 +343,23 @@ impl ChannelStore {
|
||||||
message_id: u64,
|
message_id: u64,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.channel_index
|
self.channel_states
|
||||||
.acknowledge_message_id(channel_id, message_id);
|
.entry(channel_id)
|
||||||
|
.or_insert_with(|| Default::default())
|
||||||
|
.acknowledge_message_id(message_id);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_latest_message_id(
|
||||||
|
&mut self,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
message_id: u64,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
self.channel_states
|
||||||
|
.entry(channel_id)
|
||||||
|
.or_insert_with(|| Default::default())
|
||||||
|
.update_latest_message_id(message_id);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,9 +370,25 @@ impl ChannelStore {
|
||||||
version: &clock::Global,
|
version: &clock::Global,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.channel_index
|
self.channel_states
|
||||||
.acknowledge_note_version(channel_id, epoch, version);
|
.entry(channel_id)
|
||||||
cx.notify();
|
.or_insert_with(|| Default::default())
|
||||||
|
.acknowledge_notes_version(epoch, version);
|
||||||
|
cx.notify()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_latest_notes_version(
|
||||||
|
&mut self,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
epoch: u64,
|
||||||
|
version: &clock::Global,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
self.channel_states
|
||||||
|
.entry(channel_id)
|
||||||
|
.or_insert_with(|| Default::default())
|
||||||
|
.update_latest_notes_version(epoch, version);
|
||||||
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_channel_chat(
|
pub fn open_channel_chat(
|
||||||
|
@ -454,10 +481,42 @@ impl ChannelStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_channel_admin(&self, channel_id: ChannelId) -> bool {
|
pub fn is_channel_admin(&self, channel_id: ChannelId) -> bool {
|
||||||
let Some(channel) = self.channel_for_id(channel_id) else {
|
self.channel_role(channel_id) == proto::ChannelRole::Admin
|
||||||
return false;
|
}
|
||||||
};
|
|
||||||
channel.role == proto::ChannelRole::Admin
|
pub fn is_root_channel(&self, channel_id: ChannelId) -> bool {
|
||||||
|
self.channel_index
|
||||||
|
.by_id()
|
||||||
|
.get(&channel_id)
|
||||||
|
.map_or(false, |channel| channel.is_root_channel())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_public_channel(&self, channel_id: ChannelId) -> bool {
|
||||||
|
self.channel_index
|
||||||
|
.by_id()
|
||||||
|
.get(&channel_id)
|
||||||
|
.map_or(false, |channel| {
|
||||||
|
channel.visibility == ChannelVisibility::Public
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channel_capability(&self, channel_id: ChannelId) -> Capability {
|
||||||
|
match self.channel_role(channel_id) {
|
||||||
|
ChannelRole::Admin | ChannelRole::Member => Capability::ReadWrite,
|
||||||
|
_ => Capability::ReadOnly,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channel_role(&self, channel_id: ChannelId) -> proto::ChannelRole {
|
||||||
|
maybe!({
|
||||||
|
let mut channel = self.channel_for_id(channel_id)?;
|
||||||
|
if !channel.is_root_channel() {
|
||||||
|
channel = self.channel_for_id(channel.root_id())?;
|
||||||
|
}
|
||||||
|
let root_channel_state = self.channel_states.get(&channel.id);
|
||||||
|
root_channel_state?.role
|
||||||
|
})
|
||||||
|
.unwrap_or(proto::ChannelRole::Guest)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel_participants(&self, channel_id: ChannelId) -> &[Arc<User>] {
|
pub fn channel_participants(&self, channel_id: ChannelId) -> &[Arc<User>] {
|
||||||
|
@ -508,7 +567,7 @@ impl ChannelStore {
|
||||||
pub fn move_channel(
|
pub fn move_channel(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
to: Option<ChannelId>,
|
to: ChannelId,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
|
@ -747,6 +806,36 @@ impl ChannelStore {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_update_user_channels(
|
||||||
|
this: Model<Self>,
|
||||||
|
message: TypedEnvelope<proto::UpdateUserChannels>,
|
||||||
|
_: Arc<Client>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
for buffer_version in message.payload.observed_channel_buffer_version {
|
||||||
|
let version = language::proto::deserialize_version(&buffer_version.version);
|
||||||
|
this.acknowledge_notes_version(
|
||||||
|
buffer_version.channel_id,
|
||||||
|
buffer_version.epoch,
|
||||||
|
&version,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for message_id in message.payload.observed_channel_message_id {
|
||||||
|
this.acknowledge_message_id(message_id.channel_id, message_id.message_id, cx);
|
||||||
|
}
|
||||||
|
for membership in message.payload.channel_memberships {
|
||||||
|
if let Some(role) = ChannelRole::from_i32(membership.role) {
|
||||||
|
this.channel_states
|
||||||
|
.entry(membership.channel_id)
|
||||||
|
.or_insert_with(|| ChannelState::default())
|
||||||
|
.set_role(role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_connect(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
fn handle_connect(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
self.channel_index.clear();
|
self.channel_index.clear();
|
||||||
self.channel_invitations.clear();
|
self.channel_invitations.clear();
|
||||||
|
@ -909,10 +998,7 @@ impl ChannelStore {
|
||||||
Arc::new(Channel {
|
Arc::new(Channel {
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
visibility: channel.visibility(),
|
visibility: channel.visibility(),
|
||||||
role: channel.role(),
|
|
||||||
name: channel.name.into(),
|
name: channel.name.into(),
|
||||||
unseen_note_version: None,
|
|
||||||
unseen_message_id: None,
|
|
||||||
parent_path: channel.parent_path,
|
parent_path: channel.parent_path,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -921,8 +1007,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.unseen_channel_messages.is_empty()
|
|| !payload.latest_channel_message_ids.is_empty()
|
||||||
|| !payload.unseen_channel_buffer_changes.is_empty();
|
|| !payload.latest_channel_buffer_versions.is_empty();
|
||||||
|
|
||||||
if channels_changed {
|
if channels_changed {
|
||||||
if !payload.delete_channels.is_empty() {
|
if !payload.delete_channels.is_empty() {
|
||||||
|
@ -963,20 +1049,19 @@ impl ChannelStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for unseen_buffer_change in payload.unseen_channel_buffer_changes {
|
for latest_buffer_version in payload.latest_channel_buffer_versions {
|
||||||
let version = language::proto::deserialize_version(&unseen_buffer_change.version);
|
let version = language::proto::deserialize_version(&latest_buffer_version.version);
|
||||||
index.note_changed(
|
self.channel_states
|
||||||
unseen_buffer_change.channel_id,
|
.entry(latest_buffer_version.channel_id)
|
||||||
unseen_buffer_change.epoch,
|
.or_default()
|
||||||
&version,
|
.update_latest_notes_version(latest_buffer_version.epoch, &version)
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for unseen_channel_message in payload.unseen_channel_messages {
|
for latest_channel_message in payload.latest_channel_message_ids {
|
||||||
index.new_messages(
|
self.channel_states
|
||||||
unseen_channel_message.channel_id,
|
.entry(latest_channel_message.channel_id)
|
||||||
unseen_channel_message.message_id,
|
.or_default()
|
||||||
);
|
.update_latest_message_id(latest_channel_message.message_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1025,3 +1110,69 @@ impl ChannelStore {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ChannelState {
|
||||||
|
fn set_role(&mut self, role: ChannelRole) {
|
||||||
|
self.role = Some(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_channel_buffer_changed(&self) -> bool {
|
||||||
|
if let Some(latest_version) = &self.latest_notes_versions {
|
||||||
|
if let Some(observed_version) = &self.observed_notes_versions {
|
||||||
|
latest_version.epoch > observed_version.epoch
|
||||||
|
|| latest_version
|
||||||
|
.version
|
||||||
|
.changed_since(&observed_version.version)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_new_messages(&self) -> bool {
|
||||||
|
let latest_message_id = self.latest_chat_message;
|
||||||
|
let observed_message_id = self.observed_chat_message;
|
||||||
|
|
||||||
|
latest_message_id.is_some_and(|latest_message_id| {
|
||||||
|
latest_message_id > observed_message_id.unwrap_or_default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acknowledge_message_id(&mut self, message_id: u64) {
|
||||||
|
let observed = self.observed_chat_message.get_or_insert(message_id);
|
||||||
|
*observed = (*observed).max(message_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_latest_message_id(&mut self, message_id: u64) {
|
||||||
|
self.latest_chat_message =
|
||||||
|
Some(message_id.max(self.latest_chat_message.unwrap_or_default()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acknowledge_notes_version(&mut self, epoch: u64, version: &clock::Global) {
|
||||||
|
if let Some(existing) = &mut self.observed_notes_versions {
|
||||||
|
if existing.epoch == epoch {
|
||||||
|
existing.version.join(version);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.observed_notes_versions = Some(NotesVersion {
|
||||||
|
epoch,
|
||||||
|
version: version.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_latest_notes_version(&mut self, epoch: u64, version: &clock::Global) {
|
||||||
|
if let Some(existing) = &mut self.latest_notes_versions {
|
||||||
|
if existing.epoch == epoch {
|
||||||
|
existing.version.join(version);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.latest_notes_versions = Some(NotesVersion {
|
||||||
|
epoch,
|
||||||
|
version: version.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -37,43 +37,6 @@ impl ChannelIndex {
|
||||||
channels_by_id: &mut self.channels_by_id,
|
channels_by_id: &mut self.channels_by_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn acknowledge_note_version(
|
|
||||||
&mut self,
|
|
||||||
channel_id: ChannelId,
|
|
||||||
epoch: u64,
|
|
||||||
version: &clock::Global,
|
|
||||||
) {
|
|
||||||
if let Some(channel) = self.channels_by_id.get_mut(&channel_id) {
|
|
||||||
let channel = Arc::make_mut(channel);
|
|
||||||
if let Some((unseen_epoch, unseen_version)) = &channel.unseen_note_version {
|
|
||||||
if epoch > *unseen_epoch
|
|
||||||
|| epoch == *unseen_epoch && version.observed_all(unseen_version)
|
|
||||||
{
|
|
||||||
channel.unseen_note_version = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn acknowledge_message_id(&mut self, channel_id: ChannelId, message_id: u64) {
|
|
||||||
if let Some(channel) = self.channels_by_id.get_mut(&channel_id) {
|
|
||||||
let channel = Arc::make_mut(channel);
|
|
||||||
if let Some(unseen_message_id) = channel.unseen_message_id {
|
|
||||||
if message_id >= unseen_message_id {
|
|
||||||
channel.unseen_message_id = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
|
|
||||||
insert_note_changed(&mut self.channels_by_id, channel_id, epoch, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_message(&mut self, channel_id: ChannelId, message_id: u64) {
|
|
||||||
insert_new_message(&mut self.channels_by_id, channel_id, message_id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A guard for ensuring that the paths index maintains its sort and uniqueness
|
/// A guard for ensuring that the paths index maintains its sort and uniqueness
|
||||||
|
@ -85,36 +48,25 @@ pub struct ChannelPathsInsertGuard<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ChannelPathsInsertGuard<'a> {
|
impl<'a> ChannelPathsInsertGuard<'a> {
|
||||||
pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
|
|
||||||
insert_note_changed(self.channels_by_id, channel_id, epoch, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_messages(&mut self, channel_id: ChannelId, message_id: u64) {
|
|
||||||
insert_new_message(self.channels_by_id, channel_id, message_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
|
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
|
||||||
let mut ret = false;
|
let mut ret = false;
|
||||||
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) {
|
||||||
let existing_channel = Arc::make_mut(existing_channel);
|
let existing_channel = Arc::make_mut(existing_channel);
|
||||||
|
|
||||||
ret = existing_channel.visibility != channel_proto.visibility()
|
ret = existing_channel.visibility != channel_proto.visibility()
|
||||||
|| existing_channel.role != channel_proto.role()
|
|| existing_channel.name != channel_proto.name
|
||||||
|| existing_channel.name != channel_proto.name;
|
|| existing_channel.parent_path != channel_proto.parent_path;
|
||||||
|
|
||||||
existing_channel.visibility = channel_proto.visibility();
|
existing_channel.visibility = channel_proto.visibility();
|
||||||
existing_channel.role = channel_proto.role();
|
|
||||||
existing_channel.name = channel_proto.name.into();
|
existing_channel.name = channel_proto.name.into();
|
||||||
|
existing_channel.parent_path = channel_proto.parent_path.into();
|
||||||
} else {
|
} else {
|
||||||
self.channels_by_id.insert(
|
self.channels_by_id.insert(
|
||||||
channel_proto.id,
|
channel_proto.id,
|
||||||
Arc::new(Channel {
|
Arc::new(Channel {
|
||||||
id: channel_proto.id,
|
id: channel_proto.id,
|
||||||
visibility: channel_proto.visibility(),
|
visibility: channel_proto.visibility(),
|
||||||
role: channel_proto.role(),
|
|
||||||
name: channel_proto.name.into(),
|
name: channel_proto.name.into(),
|
||||||
unseen_note_version: None,
|
|
||||||
unseen_message_id: None,
|
|
||||||
parent_path: channel_proto.parent_path,
|
parent_path: channel_proto.parent_path,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -153,32 +105,3 @@ fn channel_path_sorting_key<'a>(
|
||||||
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
|
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
|
||||||
.chain(name)
|
.chain(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_note_changed(
|
|
||||||
channels_by_id: &mut BTreeMap<ChannelId, Arc<Channel>>,
|
|
||||||
channel_id: u64,
|
|
||||||
epoch: u64,
|
|
||||||
version: &clock::Global,
|
|
||||||
) {
|
|
||||||
if let Some(channel) = channels_by_id.get_mut(&channel_id) {
|
|
||||||
let unseen_version = Arc::make_mut(channel)
|
|
||||||
.unseen_note_version
|
|
||||||
.get_or_insert((0, clock::Global::new()));
|
|
||||||
if epoch > unseen_version.0 {
|
|
||||||
*unseen_version = (epoch, version.clone());
|
|
||||||
} else {
|
|
||||||
unseen_version.1.join(version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_new_message(
|
|
||||||
channels_by_id: &mut BTreeMap<ChannelId, Arc<Channel>>,
|
|
||||||
channel_id: u64,
|
|
||||||
message_id: u64,
|
|
||||||
) {
|
|
||||||
if let Some(channel) = channels_by_id.get_mut(&channel_id) {
|
|
||||||
let unseen_message_id = Arc::make_mut(channel).unseen_message_id.get_or_insert(0);
|
|
||||||
*unseen_message_id = message_id.max(*unseen_message_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,14 +19,12 @@ fn test_update_channels(cx: &mut AppContext) {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "b".to_string(),
|
name: "b".to_string(),
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
visibility: proto::ChannelVisibility::Members as i32,
|
||||||
role: proto::ChannelRole::Admin.into(),
|
|
||||||
parent_path: Vec::new(),
|
parent_path: Vec::new(),
|
||||||
},
|
},
|
||||||
proto::Channel {
|
proto::Channel {
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "a".to_string(),
|
name: "a".to_string(),
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
visibility: proto::ChannelVisibility::Members as i32,
|
||||||
role: proto::ChannelRole::Member.into(),
|
|
||||||
parent_path: Vec::new(),
|
parent_path: Vec::new(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -38,8 +36,8 @@ fn test_update_channels(cx: &mut AppContext) {
|
||||||
&channel_store,
|
&channel_store,
|
||||||
&[
|
&[
|
||||||
//
|
//
|
||||||
(0, "a".to_string(), proto::ChannelRole::Member),
|
(0, "a".to_string()),
|
||||||
(0, "b".to_string(), proto::ChannelRole::Admin),
|
(0, "b".to_string()),
|
||||||
],
|
],
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -52,14 +50,12 @@ fn test_update_channels(cx: &mut AppContext) {
|
||||||
id: 3,
|
id: 3,
|
||||||
name: "x".to_string(),
|
name: "x".to_string(),
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
visibility: proto::ChannelVisibility::Members as i32,
|
||||||
role: proto::ChannelRole::Admin.into(),
|
|
||||||
parent_path: vec![1],
|
parent_path: vec![1],
|
||||||
},
|
},
|
||||||
proto::Channel {
|
proto::Channel {
|
||||||
id: 4,
|
id: 4,
|
||||||
name: "y".to_string(),
|
name: "y".to_string(),
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
visibility: proto::ChannelVisibility::Members as i32,
|
||||||
role: proto::ChannelRole::Member.into(),
|
|
||||||
parent_path: vec![2],
|
parent_path: vec![2],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -70,10 +66,10 @@ fn test_update_channels(cx: &mut AppContext) {
|
||||||
assert_channels(
|
assert_channels(
|
||||||
&channel_store,
|
&channel_store,
|
||||||
&[
|
&[
|
||||||
(0, "a".to_string(), proto::ChannelRole::Member),
|
(0, "a".to_string()),
|
||||||
(1, "y".to_string(), proto::ChannelRole::Member),
|
(1, "y".to_string()),
|
||||||
(0, "b".to_string(), proto::ChannelRole::Admin),
|
(0, "b".to_string()),
|
||||||
(1, "x".to_string(), proto::ChannelRole::Admin),
|
(1, "x".to_string()),
|
||||||
],
|
],
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -91,21 +87,18 @@ fn test_dangling_channel_paths(cx: &mut AppContext) {
|
||||||
id: 0,
|
id: 0,
|
||||||
name: "a".to_string(),
|
name: "a".to_string(),
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
visibility: proto::ChannelVisibility::Members as i32,
|
||||||
role: proto::ChannelRole::Admin.into(),
|
|
||||||
parent_path: vec![],
|
parent_path: vec![],
|
||||||
},
|
},
|
||||||
proto::Channel {
|
proto::Channel {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "b".to_string(),
|
name: "b".to_string(),
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
visibility: proto::ChannelVisibility::Members as i32,
|
||||||
role: proto::ChannelRole::Admin.into(),
|
|
||||||
parent_path: vec![0],
|
parent_path: vec![0],
|
||||||
},
|
},
|
||||||
proto::Channel {
|
proto::Channel {
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "c".to_string(),
|
name: "c".to_string(),
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
visibility: proto::ChannelVisibility::Members as i32,
|
||||||
role: proto::ChannelRole::Admin.into(),
|
|
||||||
parent_path: vec![0, 1],
|
parent_path: vec![0, 1],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -118,9 +111,9 @@ fn test_dangling_channel_paths(cx: &mut AppContext) {
|
||||||
&channel_store,
|
&channel_store,
|
||||||
&[
|
&[
|
||||||
//
|
//
|
||||||
(0, "a".to_string(), proto::ChannelRole::Admin),
|
(0, "a".to_string()),
|
||||||
(1, "b".to_string(), proto::ChannelRole::Admin),
|
(1, "b".to_string()),
|
||||||
(2, "c".to_string(), proto::ChannelRole::Admin),
|
(2, "c".to_string()),
|
||||||
],
|
],
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -135,11 +128,7 @@ fn test_dangling_channel_paths(cx: &mut AppContext) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make sure that the 1/2/3 path is gone
|
// Make sure that the 1/2/3 path is gone
|
||||||
assert_channels(
|
assert_channels(&channel_store, &[(0, "a".to_string())], cx);
|
||||||
&channel_store,
|
|
||||||
&[(0, "a".to_string(), proto::ChannelRole::Admin)],
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -156,18 +145,13 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||||
id: channel_id,
|
id: channel_id,
|
||||||
name: "the-channel".to_string(),
|
name: "the-channel".to_string(),
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
visibility: proto::ChannelVisibility::Members as i32,
|
||||||
role: proto::ChannelRole::Member.into(),
|
|
||||||
parent_path: vec![],
|
parent_path: vec![],
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
assert_channels(
|
assert_channels(&channel_store, &[(0, "the-channel".to_string())], cx);
|
||||||
&channel_store,
|
|
||||||
&[(0, "the-channel".to_string(), proto::ChannelRole::Member)],
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let get_users = server.receive::<proto::GetUsers>().await.unwrap();
|
let get_users = server.receive::<proto::GetUsers>().await.unwrap();
|
||||||
|
@ -368,13 +352,13 @@ fn update_channels(
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_channels(
|
fn assert_channels(
|
||||||
channel_store: &Model<ChannelStore>,
|
channel_store: &Model<ChannelStore>,
|
||||||
expected_channels: &[(usize, String, proto::ChannelRole)],
|
expected_channels: &[(usize, String)],
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
let actual = channel_store.update(cx, |store, _| {
|
let actual = channel_store.update(cx, |store, _| {
|
||||||
store
|
store
|
||||||
.ordered_channels()
|
.ordered_channels()
|
||||||
.map(|(depth, channel)| (depth, channel.name.to_string(), channel.role))
|
.map(|(depth, channel)| (depth, channel.name.to_string()))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
});
|
});
|
||||||
assert_eq!(actual, expected_channels);
|
assert_eq!(actual, expected_channels);
|
||||||
|
|
|
@ -40,7 +40,7 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tables::*;
|
pub use tables::*;
|
||||||
use tokio::sync::{Mutex, OwnedMutexGuard};
|
use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||||
|
|
||||||
pub use ids::*;
|
pub use ids::*;
|
||||||
|
@ -502,35 +502,6 @@ pub struct NewUserResult {
|
||||||
pub signup_device_id: Option<String>,
|
pub signup_device_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of moving a channel.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct MoveChannelResult {
|
|
||||||
pub previous_participants: Vec<ChannelMember>,
|
|
||||||
pub descendent_ids: Vec<ChannelId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of renaming a channel.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RenameChannelResult {
|
|
||||||
pub channel: Channel,
|
|
||||||
pub participants_to_update: HashMap<UserId, Channel>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of creating a channel.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CreateChannelResult {
|
|
||||||
pub channel: Channel,
|
|
||||||
pub participants_to_update: Vec<(UserId, ChannelsForUser)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of setting a channel's visibility.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SetChannelVisibilityResult {
|
|
||||||
pub participants_to_update: HashMap<UserId, ChannelsForUser>,
|
|
||||||
pub participants_to_remove: HashSet<UserId>,
|
|
||||||
pub channels_to_remove: Vec<ChannelId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of updating a channel membership.
|
/// The result of updating a channel membership.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MembershipUpdated {
|
pub struct MembershipUpdated {
|
||||||
|
@ -570,18 +541,16 @@ pub struct Channel {
|
||||||
pub id: ChannelId,
|
pub id: ChannelId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub visibility: ChannelVisibility,
|
pub visibility: ChannelVisibility,
|
||||||
pub role: ChannelRole,
|
|
||||||
/// parent_path is the channel ids from the root to this one (not including this one)
|
/// parent_path is the channel ids from the root to this one (not including this one)
|
||||||
pub parent_path: Vec<ChannelId>,
|
pub parent_path: Vec<ChannelId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Channel {
|
impl Channel {
|
||||||
fn from_model(value: channel::Model, role: ChannelRole) -> Self {
|
fn from_model(value: channel::Model) -> Self {
|
||||||
Channel {
|
Channel {
|
||||||
id: value.id,
|
id: value.id,
|
||||||
visibility: value.visibility,
|
visibility: value.visibility,
|
||||||
name: value.clone().name,
|
name: value.clone().name,
|
||||||
role,
|
|
||||||
parent_path: value.ancestors().collect(),
|
parent_path: value.ancestors().collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -591,7 +560,6 @@ impl Channel {
|
||||||
id: self.id.to_proto(),
|
id: self.id.to_proto(),
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
visibility: self.visibility.into(),
|
visibility: self.visibility.into(),
|
||||||
role: self.role.into(),
|
|
||||||
parent_path: self.parent_path.iter().map(|c| c.to_proto()).collect(),
|
parent_path: self.parent_path.iter().map(|c| c.to_proto()).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -617,9 +585,10 @@ impl ChannelMember {
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct ChannelsForUser {
|
pub struct ChannelsForUser {
|
||||||
pub channels: Vec<Channel>,
|
pub channels: Vec<Channel>,
|
||||||
|
pub channel_memberships: Vec<channel_member::Model>,
|
||||||
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
|
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
|
||||||
pub unseen_buffer_changes: Vec<proto::UnseenChannelBufferChange>,
|
pub latest_buffer_versions: Vec<proto::ChannelBufferVersion>,
|
||||||
pub channel_messages: Vec<proto::UnseenChannelMessage>,
|
pub latest_channel_messages: Vec<proto::ChannelMessageId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -129,6 +129,15 @@ impl ChannelRole {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn can_see_channel(&self, visibility: ChannelVisibility) -> bool {
|
||||||
|
use ChannelRole::*;
|
||||||
|
match self {
|
||||||
|
Admin | Member => true,
|
||||||
|
Guest => visibility == ChannelVisibility::Public,
|
||||||
|
Banned => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// True if the role allows access to all descendant channels
|
/// True if the role allows access to all descendant channels
|
||||||
pub fn can_see_all_descendants(&self) -> bool {
|
pub fn can_see_all_descendants(&self) -> bool {
|
||||||
use ChannelRole::*;
|
use ChannelRole::*;
|
||||||
|
|
|
@ -748,18 +748,11 @@ impl Database {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn unseen_channel_buffer_changes(
|
pub async fn latest_channel_buffer_changes(
|
||||||
&self,
|
&self,
|
||||||
user_id: UserId,
|
|
||||||
channel_ids: &[ChannelId],
|
channel_ids: &[ChannelId],
|
||||||
tx: &DatabaseTransaction,
|
tx: &DatabaseTransaction,
|
||||||
) -> Result<Vec<proto::UnseenChannelBufferChange>> {
|
) -> Result<Vec<proto::ChannelBufferVersion>> {
|
||||||
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
|
|
||||||
enum QueryIds {
|
|
||||||
ChannelId,
|
|
||||||
Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut channel_ids_by_buffer_id = HashMap::default();
|
let mut channel_ids_by_buffer_id = HashMap::default();
|
||||||
let mut rows = buffer::Entity::find()
|
let mut rows = buffer::Entity::find()
|
||||||
.filter(buffer::Column::ChannelId.is_in(channel_ids.iter().copied()))
|
.filter(buffer::Column::ChannelId.is_in(channel_ids.iter().copied()))
|
||||||
|
@ -771,51 +764,23 @@ impl Database {
|
||||||
}
|
}
|
||||||
drop(rows);
|
drop(rows);
|
||||||
|
|
||||||
let mut observed_edits_by_buffer_id = HashMap::default();
|
|
||||||
let mut rows = observed_buffer_edits::Entity::find()
|
|
||||||
.filter(observed_buffer_edits::Column::UserId.eq(user_id))
|
|
||||||
.filter(
|
|
||||||
observed_buffer_edits::Column::BufferId
|
|
||||||
.is_in(channel_ids_by_buffer_id.keys().copied()),
|
|
||||||
)
|
|
||||||
.stream(&*tx)
|
|
||||||
.await?;
|
|
||||||
while let Some(row) = rows.next().await {
|
|
||||||
let row = row?;
|
|
||||||
observed_edits_by_buffer_id.insert(row.buffer_id, row);
|
|
||||||
}
|
|
||||||
drop(rows);
|
|
||||||
|
|
||||||
let latest_operations = self
|
let latest_operations = self
|
||||||
.get_latest_operations_for_buffers(channel_ids_by_buffer_id.keys().copied(), &*tx)
|
.get_latest_operations_for_buffers(channel_ids_by_buffer_id.keys().copied(), &*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut changes = Vec::default();
|
Ok(latest_operations
|
||||||
for latest in latest_operations {
|
.iter()
|
||||||
if let Some(observed) = observed_edits_by_buffer_id.get(&latest.buffer_id) {
|
.flat_map(|op| {
|
||||||
if (
|
Some(proto::ChannelBufferVersion {
|
||||||
observed.epoch,
|
channel_id: channel_ids_by_buffer_id.get(&op.buffer_id)?.to_proto(),
|
||||||
observed.lamport_timestamp,
|
epoch: op.epoch as u64,
|
||||||
observed.replica_id,
|
|
||||||
) >= (latest.epoch, latest.lamport_timestamp, latest.replica_id)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(channel_id) = channel_ids_by_buffer_id.get(&latest.buffer_id) {
|
|
||||||
changes.push(proto::UnseenChannelBufferChange {
|
|
||||||
channel_id: channel_id.to_proto(),
|
|
||||||
epoch: latest.epoch as u64,
|
|
||||||
version: vec![proto::VectorClockEntry {
|
version: vec![proto::VectorClockEntry {
|
||||||
replica_id: latest.replica_id as u32,
|
replica_id: op.replica_id as u32,
|
||||||
timestamp: latest.lamport_timestamp as u32,
|
timestamp: op.lamport_timestamp as u32,
|
||||||
}],
|
}],
|
||||||
});
|
})
|
||||||
}
|
})
|
||||||
}
|
.collect())
|
||||||
|
|
||||||
Ok(changes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the latest operations for the buffers with the specified IDs.
|
/// Returns the latest operations for the buffers with the specified IDs.
|
||||||
|
|
|
@ -19,7 +19,7 @@ impl Database {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub async fn create_root_channel(&self, name: &str, creator_id: UserId) -> Result<ChannelId> {
|
pub async fn create_root_channel(&self, name: &str, creator_id: UserId) -> Result<ChannelId> {
|
||||||
Ok(self.create_channel(name, None, creator_id).await?.id)
|
Ok(self.create_channel(name, None, creator_id).await?.0.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -32,6 +32,7 @@ impl Database {
|
||||||
Ok(self
|
Ok(self
|
||||||
.create_channel(name, Some(parent), creator_id)
|
.create_channel(name, Some(parent), creator_id)
|
||||||
.await?
|
.await?
|
||||||
|
.0
|
||||||
.id)
|
.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,10 +42,15 @@ impl Database {
|
||||||
name: &str,
|
name: &str,
|
||||||
parent_channel_id: Option<ChannelId>,
|
parent_channel_id: Option<ChannelId>,
|
||||||
admin_id: UserId,
|
admin_id: UserId,
|
||||||
) -> Result<Channel> {
|
) -> Result<(
|
||||||
|
Channel,
|
||||||
|
Option<channel_member::Model>,
|
||||||
|
Vec<channel_member::Model>,
|
||||||
|
)> {
|
||||||
let name = Self::sanitize_channel_name(name)?;
|
let name = Self::sanitize_channel_name(name)?;
|
||||||
self.transaction(move |tx| async move {
|
self.transaction(move |tx| async move {
|
||||||
let mut parent = None;
|
let mut parent = None;
|
||||||
|
let mut membership = None;
|
||||||
|
|
||||||
if let Some(parent_channel_id) = parent_channel_id {
|
if let Some(parent_channel_id) = parent_channel_id {
|
||||||
let parent_channel = self.get_channel_internal(parent_channel_id, &*tx).await?;
|
let parent_channel = self.get_channel_internal(parent_channel_id, &*tx).await?;
|
||||||
|
@ -68,6 +74,7 @@ impl Database {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if parent.is_none() {
|
if parent.is_none() {
|
||||||
|
membership = Some(
|
||||||
channel_member::ActiveModel {
|
channel_member::ActiveModel {
|
||||||
id: ActiveValue::NotSet,
|
id: ActiveValue::NotSet,
|
||||||
channel_id: ActiveValue::Set(channel.id),
|
channel_id: ActiveValue::Set(channel.id),
|
||||||
|
@ -76,10 +83,16 @@ impl Database {
|
||||||
role: ActiveValue::Set(ChannelRole::Admin),
|
role: ActiveValue::Set(ChannelRole::Admin),
|
||||||
}
|
}
|
||||||
.insert(&*tx)
|
.insert(&*tx)
|
||||||
.await?;
|
.await?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Channel::from_model(channel, ChannelRole::Admin))
|
let channel_members = channel_member::Entity::find()
|
||||||
|
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||||
|
.all(&*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok((Channel::from_model(channel), membership, channel_members))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -122,16 +135,9 @@ impl Database {
|
||||||
);
|
);
|
||||||
} else if channel.visibility == ChannelVisibility::Public {
|
} else if channel.visibility == ChannelVisibility::Public {
|
||||||
role = Some(ChannelRole::Guest);
|
role = Some(ChannelRole::Guest);
|
||||||
let channel_to_join = self
|
|
||||||
.public_ancestors_including_self(&channel, &*tx)
|
|
||||||
.await?
|
|
||||||
.first()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(channel.clone());
|
|
||||||
|
|
||||||
channel_member::Entity::insert(channel_member::ActiveModel {
|
channel_member::Entity::insert(channel_member::ActiveModel {
|
||||||
id: ActiveValue::NotSet,
|
id: ActiveValue::NotSet,
|
||||||
channel_id: ActiveValue::Set(channel_to_join.id),
|
channel_id: ActiveValue::Set(channel.root_id()),
|
||||||
user_id: ActiveValue::Set(user_id),
|
user_id: ActiveValue::Set(user_id),
|
||||||
accepted: ActiveValue::Set(true),
|
accepted: ActiveValue::Set(true),
|
||||||
role: ActiveValue::Set(ChannelRole::Guest),
|
role: ActiveValue::Set(ChannelRole::Guest),
|
||||||
|
@ -140,7 +146,7 @@ impl Database {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
accept_invite_result = Some(
|
accept_invite_result = Some(
|
||||||
self.calculate_membership_updated(&channel_to_join, user_id, &*tx)
|
self.calculate_membership_updated(&channel, user_id, &*tx)
|
||||||
.await?,
|
.await?,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -173,76 +179,47 @@ impl Database {
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
visibility: ChannelVisibility,
|
visibility: ChannelVisibility,
|
||||||
admin_id: UserId,
|
admin_id: UserId,
|
||||||
) -> Result<SetChannelVisibilityResult> {
|
) -> Result<(Channel, Vec<channel_member::Model>)> {
|
||||||
self.transaction(move |tx| async move {
|
self.transaction(move |tx| async move {
|
||||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||||
|
|
||||||
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
|
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let previous_members = self
|
if visibility == ChannelVisibility::Public {
|
||||||
.get_channel_participant_details_internal(&channel, &*tx)
|
if let Some(parent_id) = channel.parent_id() {
|
||||||
.await?;
|
let parent = self.get_channel_internal(parent_id, &*tx).await?;
|
||||||
|
|
||||||
|
if parent.visibility != ChannelVisibility::Public {
|
||||||
|
Err(ErrorCode::BadPublicNesting
|
||||||
|
.with_tag("direction", "parent")
|
||||||
|
.anyhow())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if visibility == ChannelVisibility::Members {
|
||||||
|
if self
|
||||||
|
.get_channel_descendants_including_self(vec![channel_id], &*tx)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.any(|channel| {
|
||||||
|
channel.id != channel_id && channel.visibility == ChannelVisibility::Public
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Err(ErrorCode::BadPublicNesting
|
||||||
|
.with_tag("direction", "children")
|
||||||
|
.anyhow())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut model = channel.into_active_model();
|
let mut model = channel.into_active_model();
|
||||||
model.visibility = ActiveValue::Set(visibility);
|
model.visibility = ActiveValue::Set(visibility);
|
||||||
let channel = model.update(&*tx).await?;
|
let channel = model.update(&*tx).await?;
|
||||||
|
|
||||||
let mut participants_to_update: HashMap<UserId, ChannelsForUser> = self
|
let channel_members = channel_member::Entity::find()
|
||||||
.participants_to_notify_for_channel_change(&channel, &*tx)
|
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut channels_to_remove: Vec<ChannelId> = vec![];
|
|
||||||
let mut participants_to_remove: HashSet<UserId> = HashSet::default();
|
|
||||||
match visibility {
|
|
||||||
ChannelVisibility::Members => {
|
|
||||||
let all_descendents: Vec<ChannelId> = self
|
|
||||||
.get_channel_descendants_including_self(vec![channel_id], &*tx)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|channel| channel.id)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
channels_to_remove = channel::Entity::find()
|
|
||||||
.filter(
|
|
||||||
channel::Column::Id
|
|
||||||
.is_in(all_descendents)
|
|
||||||
.and(channel::Column::Visibility.eq(ChannelVisibility::Public)),
|
|
||||||
)
|
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|channel| channel.id)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
channels_to_remove.push(channel_id);
|
|
||||||
|
|
||||||
for member in previous_members {
|
|
||||||
if member.role.can_only_see_public_descendants() {
|
|
||||||
participants_to_remove.insert(member.user_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ChannelVisibility::Public => {
|
|
||||||
if let Some(public_parent) = self.public_parent_channel(&channel, &*tx).await? {
|
|
||||||
let parent_updates = self
|
|
||||||
.participants_to_notify_for_channel_change(&public_parent, &*tx)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for (user_id, channels) in parent_updates {
|
Ok((Channel::from_model(channel), channel_members))
|
||||||
participants_to_update.insert(user_id, channels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(SetChannelVisibilityResult {
|
|
||||||
participants_to_update,
|
|
||||||
participants_to_remove,
|
|
||||||
channels_to_remove,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -275,7 +252,7 @@ impl Database {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let members_to_notify: Vec<UserId> = channel_member::Entity::find()
|
let members_to_notify: Vec<UserId> = channel_member::Entity::find()
|
||||||
.filter(channel_member::Column::ChannelId.is_in(channel.ancestors_including_self()))
|
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||||
.select_only()
|
.select_only()
|
||||||
.column(channel_member::Column::UserId)
|
.column(channel_member::Column::UserId)
|
||||||
.distinct()
|
.distinct()
|
||||||
|
@ -312,6 +289,9 @@ impl Database {
|
||||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||||
self.check_user_is_channel_admin(&channel, inviter_id, &*tx)
|
self.check_user_is_channel_admin(&channel, inviter_id, &*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
if !channel.is_root() {
|
||||||
|
Err(ErrorCode::NotARootChannel.anyhow())?
|
||||||
|
}
|
||||||
|
|
||||||
channel_member::ActiveModel {
|
channel_member::ActiveModel {
|
||||||
id: ActiveValue::NotSet,
|
id: ActiveValue::NotSet,
|
||||||
|
@ -323,7 +303,7 @@ impl Database {
|
||||||
.insert(&*tx)
|
.insert(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let channel = Channel::from_model(channel, role);
|
let channel = Channel::from_model(channel);
|
||||||
|
|
||||||
let notifications = self
|
let notifications = self
|
||||||
.create_notification(
|
.create_notification(
|
||||||
|
@ -362,35 +342,24 @@ impl Database {
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
admin_id: UserId,
|
admin_id: UserId,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
) -> Result<RenameChannelResult> {
|
) -> Result<(Channel, Vec<channel_member::Model>)> {
|
||||||
self.transaction(move |tx| async move {
|
self.transaction(move |tx| async move {
|
||||||
let new_name = Self::sanitize_channel_name(new_name)?.to_string();
|
let new_name = Self::sanitize_channel_name(new_name)?.to_string();
|
||||||
|
|
||||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||||
let role = self
|
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
|
||||||
.check_user_is_channel_admin(&channel, admin_id, &*tx)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut model = channel.into_active_model();
|
let mut model = channel.into_active_model();
|
||||||
model.name = ActiveValue::Set(new_name.clone());
|
model.name = ActiveValue::Set(new_name.clone());
|
||||||
let channel = model.update(&*tx).await?;
|
let channel = model.update(&*tx).await?;
|
||||||
|
|
||||||
let participants = self
|
let channel_members = channel_member::Entity::find()
|
||||||
.get_channel_participant_details_internal(&channel, &*tx)
|
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||||
|
.all(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(RenameChannelResult {
|
Ok((Channel::from_model(channel), channel_members))
|
||||||
channel: Channel::from_model(channel.clone(), role),
|
|
||||||
participants_to_update: participants
|
|
||||||
.iter()
|
|
||||||
.map(|participant| {
|
|
||||||
(
|
|
||||||
participant.user_id,
|
|
||||||
Channel::from_model(channel.clone(), participant.role),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -565,10 +534,7 @@ impl Database {
|
||||||
|
|
||||||
let channels = channels
|
let channels = channels
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|channel| {
|
.filter_map(|channel| Some(Channel::from_model(channel)))
|
||||||
let role = *role_for_channel.get(&channel.id)?;
|
|
||||||
Some(Channel::from_model(channel, role))
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(channels)
|
Ok(channels)
|
||||||
|
@ -576,6 +542,26 @@ impl Database {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_channel_memberships(
|
||||||
|
&self,
|
||||||
|
user_id: UserId,
|
||||||
|
) -> Result<(Vec<channel_member::Model>, Vec<channel::Model>)> {
|
||||||
|
self.transaction(|tx| async move {
|
||||||
|
let memberships = channel_member::Entity::find()
|
||||||
|
.filter(channel_member::Column::UserId.eq(user_id))
|
||||||
|
.all(&*tx)
|
||||||
|
.await?;
|
||||||
|
let channels = self
|
||||||
|
.get_channel_descendants_including_self(
|
||||||
|
memberships.iter().map(|m| m.channel_id),
|
||||||
|
&*tx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok((memberships, channels))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns all channels for the user with the given ID.
|
/// Returns all channels for the user with the given ID.
|
||||||
pub async fn get_channels_for_user(&self, user_id: UserId) -> Result<ChannelsForUser> {
|
pub async fn get_channels_for_user(&self, user_id: UserId) -> Result<ChannelsForUser> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
|
@ -594,12 +580,16 @@ impl Database {
|
||||||
ancestor_channel: Option<&channel::Model>,
|
ancestor_channel: Option<&channel::Model>,
|
||||||
tx: &DatabaseTransaction,
|
tx: &DatabaseTransaction,
|
||||||
) -> Result<ChannelsForUser> {
|
) -> Result<ChannelsForUser> {
|
||||||
let channel_memberships = channel_member::Entity::find()
|
let mut filter = channel_member::Column::UserId
|
||||||
.filter(
|
|
||||||
channel_member::Column::UserId
|
|
||||||
.eq(user_id)
|
.eq(user_id)
|
||||||
.and(channel_member::Column::Accepted.eq(true)),
|
.and(channel_member::Column::Accepted.eq(true));
|
||||||
)
|
|
||||||
|
if let Some(ancestor) = ancestor_channel {
|
||||||
|
filter = filter.and(channel_member::Column::ChannelId.eq(ancestor.root_id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel_memberships = channel_member::Entity::find()
|
||||||
|
.filter(filter)
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -610,56 +600,20 @@ impl Database {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut roles_by_channel_id: HashMap<ChannelId, ChannelRole> = HashMap::default();
|
let roles_by_channel_id = channel_memberships
|
||||||
for membership in channel_memberships.iter() {
|
.iter()
|
||||||
roles_by_channel_id.insert(membership.channel_id, membership.role);
|
.map(|membership| (membership.channel_id, membership.role))
|
||||||
}
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
let mut visible_channel_ids: HashSet<ChannelId> = HashSet::default();
|
|
||||||
|
|
||||||
let channels: Vec<Channel> = descendants
|
let channels: Vec<Channel> = descendants
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|channel| {
|
.filter_map(|channel| {
|
||||||
let parent_role = channel
|
let parent_role = roles_by_channel_id.get(&channel.root_id())?;
|
||||||
.parent_id()
|
if parent_role.can_see_channel(channel.visibility) {
|
||||||
.and_then(|parent_id| roles_by_channel_id.get(&parent_id));
|
Some(Channel::from_model(channel))
|
||||||
|
|
||||||
let role = if let Some(parent_role) = parent_role {
|
|
||||||
let role = if let Some(existing_role) = roles_by_channel_id.get(&channel.id) {
|
|
||||||
existing_role.max(*parent_role)
|
|
||||||
} else {
|
} else {
|
||||||
*parent_role
|
None
|
||||||
};
|
|
||||||
roles_by_channel_id.insert(channel.id, role);
|
|
||||||
role
|
|
||||||
} else {
|
|
||||||
*roles_by_channel_id.get(&channel.id)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let can_see_parent_paths = role.can_see_all_descendants()
|
|
||||||
|| role.can_only_see_public_descendants()
|
|
||||||
&& channel.visibility == ChannelVisibility::Public;
|
|
||||||
if !can_see_parent_paths {
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visible_channel_ids.insert(channel.id);
|
|
||||||
|
|
||||||
if let Some(ancestor) = ancestor_channel {
|
|
||||||
if !channel
|
|
||||||
.ancestors_including_self()
|
|
||||||
.any(|id| id == ancestor.id)
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut channel = Channel::from_model(channel, role);
|
|
||||||
channel
|
|
||||||
.parent_path
|
|
||||||
.retain(|id| visible_channel_ids.contains(&id));
|
|
||||||
|
|
||||||
Some(channel)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -687,89 +641,21 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
let channel_ids = channels.iter().map(|c| c.id).collect::<Vec<_>>();
|
let channel_ids = channels.iter().map(|c| c.id).collect::<Vec<_>>();
|
||||||
let channel_buffer_changes = self
|
let latest_buffer_versions = self
|
||||||
.unseen_channel_buffer_changes(user_id, &channel_ids, &*tx)
|
.latest_channel_buffer_changes(&channel_ids, &*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let unseen_messages = self
|
let latest_messages = self.latest_channel_messages(&channel_ids, &*tx).await?;
|
||||||
.unseen_channel_messages(user_id, &channel_ids, &*tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(ChannelsForUser {
|
Ok(ChannelsForUser {
|
||||||
|
channel_memberships,
|
||||||
channels,
|
channels,
|
||||||
channel_participants,
|
channel_participants,
|
||||||
unseen_buffer_changes: channel_buffer_changes,
|
latest_buffer_versions,
|
||||||
channel_messages: unseen_messages,
|
latest_channel_messages: latest_messages,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new_participants_to_notify(
|
|
||||||
&self,
|
|
||||||
parent_channel_id: ChannelId,
|
|
||||||
) -> Result<Vec<(UserId, ChannelsForUser)>> {
|
|
||||||
self.weak_transaction(|tx| async move {
|
|
||||||
let parent_channel = self.get_channel_internal(parent_channel_id, &*tx).await?;
|
|
||||||
self.participants_to_notify_for_channel_change(&parent_channel, &*tx)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this is very expensive, and we should rethink
|
|
||||||
async fn participants_to_notify_for_channel_change(
|
|
||||||
&self,
|
|
||||||
new_parent: &channel::Model,
|
|
||||||
tx: &DatabaseTransaction,
|
|
||||||
) -> Result<Vec<(UserId, ChannelsForUser)>> {
|
|
||||||
let mut results: Vec<(UserId, ChannelsForUser)> = Vec::new();
|
|
||||||
|
|
||||||
let members = self
|
|
||||||
.get_channel_participant_details_internal(new_parent, &*tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for member in members.iter() {
|
|
||||||
if !member.role.can_see_all_descendants() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
results.push((
|
|
||||||
member.user_id,
|
|
||||||
self.get_user_channels(member.user_id, Some(new_parent), &*tx)
|
|
||||||
.await?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
let public_parents = self
|
|
||||||
.public_ancestors_including_self(new_parent, &*tx)
|
|
||||||
.await?;
|
|
||||||
let public_parent = public_parents.last();
|
|
||||||
|
|
||||||
let Some(public_parent) = public_parent else {
|
|
||||||
return Ok(results);
|
|
||||||
};
|
|
||||||
|
|
||||||
// could save some time in the common case by skipping this if the
|
|
||||||
// new channel is not public and has no public descendants.
|
|
||||||
let public_members = if public_parent == new_parent {
|
|
||||||
members
|
|
||||||
} else {
|
|
||||||
self.get_channel_participant_details_internal(public_parent, &*tx)
|
|
||||||
.await?
|
|
||||||
};
|
|
||||||
|
|
||||||
for member in public_members {
|
|
||||||
if !member.role.can_only_see_public_descendants() {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
results.push((
|
|
||||||
member.user_id,
|
|
||||||
self.get_user_channels(member.user_id, Some(public_parent), &*tx)
|
|
||||||
.await?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the role for the specified channel member.
|
/// Sets the role for the specified channel member.
|
||||||
pub async fn set_channel_member_role(
|
pub async fn set_channel_member_role(
|
||||||
&self,
|
&self,
|
||||||
|
@ -807,7 +693,7 @@ impl Database {
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(SetMemberRoleResult::InviteUpdated(Channel::from_model(
|
Ok(SetMemberRoleResult::InviteUpdated(Channel::from_model(
|
||||||
channel, role,
|
channel,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -837,22 +723,30 @@ impl Database {
|
||||||
if role == ChannelRole::Admin {
|
if role == ChannelRole::Admin {
|
||||||
Ok(members
|
Ok(members
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|channel_member| channel_member.to_proto())
|
.map(|channel_member| proto::ChannelMember {
|
||||||
|
role: channel_member.role.into(),
|
||||||
|
user_id: channel_member.user_id.to_proto(),
|
||||||
|
kind: if channel_member.accepted {
|
||||||
|
Kind::Member
|
||||||
|
} else {
|
||||||
|
Kind::Invitee
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
})
|
||||||
.collect())
|
.collect())
|
||||||
} else {
|
} else {
|
||||||
return Ok(members
|
return Ok(members
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|member| {
|
.filter_map(|member| {
|
||||||
if member.kind == proto::channel_member::Kind::Invitee {
|
if !member.accepted {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(ChannelMember {
|
Some(proto::ChannelMember {
|
||||||
role: member.role,
|
role: member.role.into(),
|
||||||
user_id: member.user_id,
|
user_id: member.user_id.to_proto(),
|
||||||
kind: proto::channel_member::Kind::Member,
|
kind: Kind::Member.into(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map(|channel_member| channel_member.to_proto())
|
|
||||||
.collect());
|
.collect());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -861,83 +755,11 @@ impl Database {
|
||||||
&self,
|
&self,
|
||||||
channel: &channel::Model,
|
channel: &channel::Model,
|
||||||
tx: &DatabaseTransaction,
|
tx: &DatabaseTransaction,
|
||||||
) -> Result<Vec<ChannelMember>> {
|
) -> Result<Vec<channel_member::Model>> {
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
Ok(channel_member::Entity::find()
|
||||||
enum QueryMemberDetails {
|
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||||
UserId,
|
.all(tx)
|
||||||
Role,
|
.await?)
|
||||||
IsDirectMember,
|
|
||||||
Accepted,
|
|
||||||
Visibility,
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut stream = channel_member::Entity::find()
|
|
||||||
.left_join(channel::Entity)
|
|
||||||
.filter(channel_member::Column::ChannelId.is_in(channel.ancestors_including_self()))
|
|
||||||
.select_only()
|
|
||||||
.column(channel_member::Column::UserId)
|
|
||||||
.column(channel_member::Column::Role)
|
|
||||||
.column_as(
|
|
||||||
channel_member::Column::ChannelId.eq(channel.id),
|
|
||||||
QueryMemberDetails::IsDirectMember,
|
|
||||||
)
|
|
||||||
.column(channel_member::Column::Accepted)
|
|
||||||
.column(channel::Column::Visibility)
|
|
||||||
.into_values::<_, QueryMemberDetails>()
|
|
||||||
.stream(&*tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut user_details: HashMap<UserId, ChannelMember> = HashMap::default();
|
|
||||||
|
|
||||||
while let Some(user_membership) = stream.next().await {
|
|
||||||
let (user_id, channel_role, is_direct_member, is_invite_accepted, visibility): (
|
|
||||||
UserId,
|
|
||||||
ChannelRole,
|
|
||||||
bool,
|
|
||||||
bool,
|
|
||||||
ChannelVisibility,
|
|
||||||
) = user_membership?;
|
|
||||||
let kind = match (is_direct_member, is_invite_accepted) {
|
|
||||||
(true, true) => proto::channel_member::Kind::Member,
|
|
||||||
(true, false) => proto::channel_member::Kind::Invitee,
|
|
||||||
(false, true) => proto::channel_member::Kind::AncestorMember,
|
|
||||||
(false, false) => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
if channel_role == ChannelRole::Guest
|
|
||||||
&& visibility != ChannelVisibility::Public
|
|
||||||
&& channel.visibility != ChannelVisibility::Public
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(details_mut) = user_details.get_mut(&user_id) {
|
|
||||||
if channel_role.should_override(details_mut.role) {
|
|
||||||
details_mut.role = channel_role;
|
|
||||||
}
|
|
||||||
if kind == Kind::Member {
|
|
||||||
details_mut.kind = kind;
|
|
||||||
// the UI is going to be a bit confusing if you already have permissions
|
|
||||||
// that are greater than or equal to the ones you're being invited to.
|
|
||||||
} else if kind == Kind::Invitee && details_mut.kind == Kind::AncestorMember {
|
|
||||||
details_mut.kind = kind;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
user_details.insert(
|
|
||||||
user_id,
|
|
||||||
ChannelMember {
|
|
||||||
user_id,
|
|
||||||
kind,
|
|
||||||
role: channel_role,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(user_details
|
|
||||||
.into_iter()
|
|
||||||
.map(|(_, details)| details)
|
|
||||||
.collect())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the participants in the given channel.
|
/// Returns the participants in the given channel.
|
||||||
|
@ -1016,7 +838,7 @@ impl Database {
|
||||||
tx: &DatabaseTransaction,
|
tx: &DatabaseTransaction,
|
||||||
) -> Result<Option<channel_member::Model>> {
|
) -> Result<Option<channel_member::Model>> {
|
||||||
let row = channel_member::Entity::find()
|
let row = channel_member::Entity::find()
|
||||||
.filter(channel_member::Column::ChannelId.is_in(channel.ancestors_including_self()))
|
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||||
.filter(channel_member::Column::UserId.eq(user_id))
|
.filter(channel_member::Column::UserId.eq(user_id))
|
||||||
.filter(channel_member::Column::Accepted.eq(false))
|
.filter(channel_member::Column::Accepted.eq(false))
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
|
@ -1025,33 +847,6 @@ impl Database {
|
||||||
Ok(row)
|
Ok(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn public_parent_channel(
|
|
||||||
&self,
|
|
||||||
channel: &channel::Model,
|
|
||||||
tx: &DatabaseTransaction,
|
|
||||||
) -> Result<Option<channel::Model>> {
|
|
||||||
let mut path = self.public_ancestors_including_self(channel, &*tx).await?;
|
|
||||||
if path.last().unwrap().id == channel.id {
|
|
||||||
path.pop();
|
|
||||||
}
|
|
||||||
Ok(path.pop())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn public_ancestors_including_self(
|
|
||||||
&self,
|
|
||||||
channel: &channel::Model,
|
|
||||||
tx: &DatabaseTransaction,
|
|
||||||
) -> Result<Vec<channel::Model>> {
|
|
||||||
let visible_channels = channel::Entity::find()
|
|
||||||
.filter(channel::Column::Id.is_in(channel.ancestors_including_self()))
|
|
||||||
.filter(channel::Column::Visibility.eq(ChannelVisibility::Public))
|
|
||||||
.order_by_asc(channel::Column::ParentPath)
|
|
||||||
.all(&*tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(visible_channels)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the role for a user in the given channel.
|
/// Returns the role for a user in the given channel.
|
||||||
pub async fn channel_role_for_user(
|
pub async fn channel_role_for_user(
|
||||||
&self,
|
&self,
|
||||||
|
@ -1059,77 +854,25 @@ impl Database {
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
tx: &DatabaseTransaction,
|
tx: &DatabaseTransaction,
|
||||||
) -> Result<Option<ChannelRole>> {
|
) -> Result<Option<ChannelRole>> {
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
let membership = channel_member::Entity::find()
|
||||||
enum QueryChannelMembership {
|
|
||||||
ChannelId,
|
|
||||||
Role,
|
|
||||||
Visibility,
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut rows = channel_member::Entity::find()
|
|
||||||
.left_join(channel::Entity)
|
|
||||||
.filter(
|
.filter(
|
||||||
channel_member::Column::ChannelId
|
channel_member::Column::ChannelId
|
||||||
.is_in(channel.ancestors_including_self())
|
.eq(channel.root_id())
|
||||||
.and(channel_member::Column::UserId.eq(user_id))
|
.and(channel_member::Column::UserId.eq(user_id))
|
||||||
.and(channel_member::Column::Accepted.eq(true)),
|
.and(channel_member::Column::Accepted.eq(true)),
|
||||||
)
|
)
|
||||||
.select_only()
|
.one(&*tx)
|
||||||
.column(channel_member::Column::ChannelId)
|
|
||||||
.column(channel_member::Column::Role)
|
|
||||||
.column(channel::Column::Visibility)
|
|
||||||
.into_values::<_, QueryChannelMembership>()
|
|
||||||
.stream(&*tx)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut user_role: Option<ChannelRole> = None;
|
let Some(membership) = membership else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
let mut is_participant = false;
|
if !membership.role.can_see_channel(channel.visibility) {
|
||||||
let mut current_channel_visibility = None;
|
return Ok(None);
|
||||||
|
|
||||||
// note these channels are not iterated in any particular order,
|
|
||||||
// our current logic takes the highest permission available.
|
|
||||||
while let Some(row) = rows.next().await {
|
|
||||||
let (membership_channel, role, visibility): (
|
|
||||||
ChannelId,
|
|
||||||
ChannelRole,
|
|
||||||
ChannelVisibility,
|
|
||||||
) = row?;
|
|
||||||
|
|
||||||
match role {
|
|
||||||
ChannelRole::Admin | ChannelRole::Member | ChannelRole::Banned => {
|
|
||||||
if let Some(users_role) = user_role {
|
|
||||||
user_role = Some(users_role.max(role));
|
|
||||||
} else {
|
|
||||||
user_role = Some(role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ChannelRole::Guest if visibility == ChannelVisibility::Public => {
|
|
||||||
is_participant = true
|
|
||||||
}
|
|
||||||
ChannelRole::Guest => {}
|
|
||||||
}
|
|
||||||
if channel.id == membership_channel {
|
|
||||||
current_channel_visibility = Some(visibility);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// free up database connection
|
|
||||||
drop(rows);
|
|
||||||
|
|
||||||
if is_participant && user_role.is_none() {
|
|
||||||
if current_channel_visibility.is_none() {
|
|
||||||
current_channel_visibility = channel::Entity::find()
|
|
||||||
.filter(channel::Column::Id.eq(channel.id))
|
|
||||||
.one(&*tx)
|
|
||||||
.await?
|
|
||||||
.map(|channel| channel.visibility);
|
|
||||||
}
|
|
||||||
if current_channel_visibility == Some(ChannelVisibility::Public) {
|
|
||||||
user_role = Some(ChannelRole::Guest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(user_role)
|
Ok(Some(membership.role))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the descendants of the given set if channels, ordered by their
|
// Get the descendants of the given set if channels, ordered by their
|
||||||
|
@ -1182,11 +925,10 @@ impl Database {
|
||||||
pub async fn get_channel(&self, channel_id: ChannelId, user_id: UserId) -> Result<Channel> {
|
pub async fn get_channel(&self, channel_id: ChannelId, user_id: UserId) -> Result<Channel> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||||
let role = self
|
self.check_user_is_channel_participant(&channel, user_id, &*tx)
|
||||||
.check_user_is_channel_participant(&channel, user_id, &*tx)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Channel::from_model(channel, role))
|
Ok(Channel::from_model(channel))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -1243,61 +985,39 @@ impl Database {
|
||||||
pub async fn move_channel(
|
pub async fn move_channel(
|
||||||
&self,
|
&self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
new_parent_id: Option<ChannelId>,
|
new_parent_id: ChannelId,
|
||||||
admin_id: UserId,
|
admin_id: UserId,
|
||||||
) -> Result<Option<MoveChannelResult>> {
|
) -> Result<(Vec<Channel>, Vec<channel_member::Model>)> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||||
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
|
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let new_parent_path;
|
|
||||||
let new_parent_channel;
|
|
||||||
if let Some(new_parent_id) = new_parent_id {
|
|
||||||
let new_parent = self.get_channel_internal(new_parent_id, &*tx).await?;
|
let new_parent = self.get_channel_internal(new_parent_id, &*tx).await?;
|
||||||
self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
|
|
||||||
.await?;
|
if new_parent.root_id() != channel.root_id() {
|
||||||
|
Err(anyhow!(ErrorCode::WrongMoveTarget))?;
|
||||||
|
}
|
||||||
|
|
||||||
if new_parent
|
if new_parent
|
||||||
.ancestors_including_self()
|
.ancestors_including_self()
|
||||||
.any(|id| id == channel.id)
|
.any(|id| id == channel.id)
|
||||||
{
|
{
|
||||||
Err(anyhow!("cannot move a channel into one of its descendants"))?;
|
Err(anyhow!(ErrorCode::CircularNesting))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_parent_path = new_parent.path();
|
if channel.visibility == ChannelVisibility::Public
|
||||||
new_parent_channel = Some(new_parent);
|
&& new_parent.visibility != ChannelVisibility::Public
|
||||||
} else {
|
{
|
||||||
new_parent_path = String::new();
|
Err(anyhow!(ErrorCode::BadPublicNesting))?;
|
||||||
new_parent_channel = None;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let previous_participants = self
|
|
||||||
.get_channel_participant_details_internal(&channel, &*tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
|
let root_id = channel.root_id();
|
||||||
let old_path = format!("{}{}/", channel.parent_path, channel.id);
|
let old_path = format!("{}{}/", channel.parent_path, channel.id);
|
||||||
let new_path = format!("{}{}/", new_parent_path, channel.id);
|
let new_path = format!("{}{}/", new_parent.path(), channel.id);
|
||||||
|
|
||||||
if old_path == new_path {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut model = channel.into_active_model();
|
let mut model = channel.into_active_model();
|
||||||
model.parent_path = ActiveValue::Set(new_parent_path);
|
model.parent_path = ActiveValue::Set(new_parent.path());
|
||||||
model.update(&*tx).await?;
|
let channel = model.update(&*tx).await?;
|
||||||
|
|
||||||
if new_parent_channel.is_none() {
|
|
||||||
channel_member::ActiveModel {
|
|
||||||
id: ActiveValue::NotSet,
|
|
||||||
channel_id: ActiveValue::Set(channel_id),
|
|
||||||
user_id: ActiveValue::Set(admin_id),
|
|
||||||
accepted: ActiveValue::Set(true),
|
|
||||||
role: ActiveValue::Set(ChannelRole::Admin),
|
|
||||||
}
|
|
||||||
.insert(&*tx)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let descendent_ids =
|
let descendent_ids =
|
||||||
ChannelId::find_by_statement::<QueryIds>(Statement::from_sql_and_values(
|
ChannelId::find_by_statement::<QueryIds>(Statement::from_sql_and_values(
|
||||||
|
@ -1312,10 +1032,22 @@ impl Database {
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Some(MoveChannelResult {
|
let all_moved_ids = Some(channel.id).into_iter().chain(descendent_ids);
|
||||||
previous_participants,
|
|
||||||
descendent_ids,
|
let channels = channel::Entity::find()
|
||||||
}))
|
.filter(channel::Column::Id.is_in(all_moved_ids))
|
||||||
|
.all(&*tx)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| Channel::from_model(c))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let channel_members = channel_member::Entity::find()
|
||||||
|
.filter(channel_member::Column::ChannelId.eq(root_id))
|
||||||
|
.all(&*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok((channels, channel_members))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -385,25 +385,11 @@ impl Database {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the unseen messages for the given user in the specified channels.
|
pub async fn latest_channel_messages(
|
||||||
pub async fn unseen_channel_messages(
|
|
||||||
&self,
|
&self,
|
||||||
user_id: UserId,
|
|
||||||
channel_ids: &[ChannelId],
|
channel_ids: &[ChannelId],
|
||||||
tx: &DatabaseTransaction,
|
tx: &DatabaseTransaction,
|
||||||
) -> Result<Vec<proto::UnseenChannelMessage>> {
|
) -> Result<Vec<proto::ChannelMessageId>> {
|
||||||
let mut observed_messages_by_channel_id = HashMap::default();
|
|
||||||
let mut rows = observed_channel_messages::Entity::find()
|
|
||||||
.filter(observed_channel_messages::Column::UserId.eq(user_id))
|
|
||||||
.filter(observed_channel_messages::Column::ChannelId.is_in(channel_ids.iter().copied()))
|
|
||||||
.stream(&*tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
while let Some(row) = rows.next().await {
|
|
||||||
let row = row?;
|
|
||||||
observed_messages_by_channel_id.insert(row.channel_id, row);
|
|
||||||
}
|
|
||||||
drop(rows);
|
|
||||||
let mut values = String::new();
|
let mut values = String::new();
|
||||||
for id in channel_ids {
|
for id in channel_ids {
|
||||||
if !values.is_empty() {
|
if !values.is_empty() {
|
||||||
|
@ -413,7 +399,7 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
if values.is_empty() {
|
if values.is_empty() {
|
||||||
return Ok(Default::default());
|
return Ok(Vec::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
|
@ -437,26 +423,20 @@ impl Database {
|
||||||
);
|
);
|
||||||
|
|
||||||
let stmt = Statement::from_string(self.pool.get_database_backend(), sql);
|
let stmt = Statement::from_string(self.pool.get_database_backend(), sql);
|
||||||
let last_messages = channel_message::Model::find_by_statement(stmt)
|
let mut last_messages = channel_message::Model::find_by_statement(stmt)
|
||||||
.all(&*tx)
|
.stream(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut changes = Vec::new();
|
let mut results = Vec::new();
|
||||||
for last_message in last_messages {
|
while let Some(result) = last_messages.next().await {
|
||||||
if let Some(observed_message) =
|
let message = result?;
|
||||||
observed_messages_by_channel_id.get(&last_message.channel_id)
|
results.push(proto::ChannelMessageId {
|
||||||
{
|
channel_id: message.channel_id.to_proto(),
|
||||||
if observed_message.channel_message_id == last_message.id {
|
message_id: message.id.to_proto(),
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
changes.push(proto::UnseenChannelMessage {
|
|
||||||
channel_id: last_message.channel_id.to_proto(),
|
|
||||||
message_id: last_message.id.to_proto(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(changes)
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the channel message with the given ID.
|
/// Removes the channel message with the given ID.
|
||||||
|
|
|
@ -17,6 +17,14 @@ impl Model {
|
||||||
self.ancestors().last()
|
self.ancestors().last()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_root(&self) -> bool {
|
||||||
|
self.parent_path.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root_id(&self) -> ChannelId {
|
||||||
|
self.ancestors().next().unwrap_or(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ancestors(&self) -> impl Iterator<Item = ChannelId> + '_ {
|
pub fn ancestors(&self) -> impl Iterator<Item = ChannelId> + '_ {
|
||||||
self.parent_path
|
self.parent_path
|
||||||
.trim_end_matches('/')
|
.trim_end_matches('/')
|
||||||
|
|
|
@ -150,14 +150,13 @@ impl Drop for TestDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn channel_tree(channels: &[(ChannelId, &[ChannelId], &'static str, ChannelRole)]) -> Vec<Channel> {
|
fn channel_tree(channels: &[(ChannelId, &[ChannelId], &'static str)]) -> Vec<Channel> {
|
||||||
channels
|
channels
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, parent_path, name, role)| Channel {
|
.map(|(id, parent_path, name)| Channel {
|
||||||
id: *id,
|
id: *id,
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
visibility: ChannelVisibility::Members,
|
visibility: ChannelVisibility::Members,
|
||||||
role: *role,
|
|
||||||
parent_path: parent_path.to_vec(),
|
parent_path: parent_path.to_vec(),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -330,8 +330,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||||
.transaction(|tx| {
|
.transaction(|tx| {
|
||||||
let buffers = &buffers;
|
let buffers = &buffers;
|
||||||
async move {
|
async move {
|
||||||
db.unseen_channel_buffer_changes(
|
db.latest_channel_buffer_changes(
|
||||||
observer_id,
|
|
||||||
&[
|
&[
|
||||||
buffers[0].channel_id,
|
buffers[0].channel_id,
|
||||||
buffers[1].channel_id,
|
buffers[1].channel_id,
|
||||||
|
@ -348,12 +347,12 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||||
pretty_assertions::assert_eq!(
|
pretty_assertions::assert_eq!(
|
||||||
buffer_changes,
|
buffer_changes,
|
||||||
[
|
[
|
||||||
rpc::proto::UnseenChannelBufferChange {
|
rpc::proto::ChannelBufferVersion {
|
||||||
channel_id: buffers[0].channel_id.to_proto(),
|
channel_id: buffers[0].channel_id.to_proto(),
|
||||||
epoch: 0,
|
epoch: 0,
|
||||||
version: serialize_version(&text_buffers[0].version()),
|
version: serialize_version(&text_buffers[0].version()),
|
||||||
},
|
},
|
||||||
rpc::proto::UnseenChannelBufferChange {
|
rpc::proto::ChannelBufferVersion {
|
||||||
channel_id: buffers[1].channel_id.to_proto(),
|
channel_id: buffers[1].channel_id.to_proto(),
|
||||||
epoch: 1,
|
epoch: 1,
|
||||||
version: serialize_version(&text_buffers[1].version())
|
version: serialize_version(&text_buffers[1].version())
|
||||||
|
@ -362,99 +361,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||||
== buffer_changes[1].version.first().unwrap().replica_id)
|
== buffer_changes[1].version.first().unwrap().replica_id)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
},
|
},
|
||||||
rpc::proto::UnseenChannelBufferChange {
|
rpc::proto::ChannelBufferVersion {
|
||||||
channel_id: buffers[2].channel_id.to_proto(),
|
|
||||||
epoch: 0,
|
|
||||||
version: serialize_version(&text_buffers[2].version()),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
db.observe_buffer_version(
|
|
||||||
buffers[1].id,
|
|
||||||
observer_id,
|
|
||||||
1,
|
|
||||||
serialize_version(&text_buffers[1].version()).as_slice(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let buffer_changes = db
|
|
||||||
.transaction(|tx| {
|
|
||||||
let buffers = &buffers;
|
|
||||||
async move {
|
|
||||||
db.unseen_channel_buffer_changes(
|
|
||||||
observer_id,
|
|
||||||
&[
|
|
||||||
buffers[0].channel_id,
|
|
||||||
buffers[1].channel_id,
|
|
||||||
buffers[2].channel_id,
|
|
||||||
],
|
|
||||||
&*tx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
buffer_changes,
|
|
||||||
[
|
|
||||||
rpc::proto::UnseenChannelBufferChange {
|
|
||||||
channel_id: buffers[0].channel_id.to_proto(),
|
|
||||||
epoch: 0,
|
|
||||||
version: serialize_version(&text_buffers[0].version()),
|
|
||||||
},
|
|
||||||
rpc::proto::UnseenChannelBufferChange {
|
|
||||||
channel_id: buffers[2].channel_id.to_proto(),
|
|
||||||
epoch: 0,
|
|
||||||
version: serialize_version(&text_buffers[2].version()),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Observe an earlier version of the buffer.
|
|
||||||
db.observe_buffer_version(
|
|
||||||
buffers[1].id,
|
|
||||||
observer_id,
|
|
||||||
1,
|
|
||||||
&[rpc::proto::VectorClockEntry {
|
|
||||||
replica_id: 0,
|
|
||||||
timestamp: 0,
|
|
||||||
}],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let buffer_changes = db
|
|
||||||
.transaction(|tx| {
|
|
||||||
let buffers = &buffers;
|
|
||||||
async move {
|
|
||||||
db.unseen_channel_buffer_changes(
|
|
||||||
observer_id,
|
|
||||||
&[
|
|
||||||
buffers[0].channel_id,
|
|
||||||
buffers[1].channel_id,
|
|
||||||
buffers[2].channel_id,
|
|
||||||
],
|
|
||||||
&*tx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
buffer_changes,
|
|
||||||
[
|
|
||||||
rpc::proto::UnseenChannelBufferChange {
|
|
||||||
channel_id: buffers[0].channel_id.to_proto(),
|
|
||||||
epoch: 0,
|
|
||||||
version: serialize_version(&text_buffers[0].version()),
|
|
||||||
},
|
|
||||||
rpc::proto::UnseenChannelBufferChange {
|
|
||||||
channel_id: buffers[2].channel_id.to_proto(),
|
channel_id: buffers[2].channel_id.to_proto(),
|
||||||
epoch: 0,
|
epoch: 0,
|
||||||
version: serialize_version(&text_buffers[2].version()),
|
version: serialize_version(&text_buffers[2].version()),
|
||||||
|
|
|
@ -62,23 +62,13 @@ async fn test_channels(db: &Arc<Database>) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.channels,
|
result.channels,
|
||||||
channel_tree(&[
|
channel_tree(&[
|
||||||
(zed_id, &[], "zed", ChannelRole::Admin),
|
(zed_id, &[], "zed"),
|
||||||
(crdb_id, &[zed_id], "crdb", ChannelRole::Admin),
|
(crdb_id, &[zed_id], "crdb"),
|
||||||
(
|
(livestreaming_id, &[zed_id], "livestreaming",),
|
||||||
livestreaming_id,
|
(replace_id, &[zed_id], "replace"),
|
||||||
&[zed_id],
|
(rust_id, &[], "rust"),
|
||||||
"livestreaming",
|
(cargo_id, &[rust_id], "cargo"),
|
||||||
ChannelRole::Admin
|
(cargo_ra_id, &[rust_id, cargo_id], "cargo-ra",)
|
||||||
),
|
|
||||||
(replace_id, &[zed_id], "replace", ChannelRole::Admin),
|
|
||||||
(rust_id, &[], "rust", ChannelRole::Admin),
|
|
||||||
(cargo_id, &[rust_id], "cargo", ChannelRole::Admin),
|
|
||||||
(
|
|
||||||
cargo_ra_id,
|
|
||||||
&[rust_id, cargo_id],
|
|
||||||
"cargo-ra",
|
|
||||||
ChannelRole::Admin
|
|
||||||
)
|
|
||||||
],)
|
],)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -86,15 +76,10 @@ async fn test_channels(db: &Arc<Database>) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.channels,
|
result.channels,
|
||||||
channel_tree(&[
|
channel_tree(&[
|
||||||
(zed_id, &[], "zed", ChannelRole::Member),
|
(zed_id, &[], "zed"),
|
||||||
(crdb_id, &[zed_id], "crdb", ChannelRole::Member),
|
(crdb_id, &[zed_id], "crdb"),
|
||||||
(
|
(livestreaming_id, &[zed_id], "livestreaming",),
|
||||||
livestreaming_id,
|
(replace_id, &[zed_id], "replace")
|
||||||
&[zed_id],
|
|
||||||
"livestreaming",
|
|
||||||
ChannelRole::Member
|
|
||||||
),
|
|
||||||
(replace_id, &[zed_id], "replace", ChannelRole::Member)
|
|
||||||
],)
|
],)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -112,15 +97,10 @@ async fn test_channels(db: &Arc<Database>) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.channels,
|
result.channels,
|
||||||
channel_tree(&[
|
channel_tree(&[
|
||||||
(zed_id, &[], "zed", ChannelRole::Admin),
|
(zed_id, &[], "zed"),
|
||||||
(crdb_id, &[zed_id], "crdb", ChannelRole::Admin),
|
(crdb_id, &[zed_id], "crdb"),
|
||||||
(
|
(livestreaming_id, &[zed_id], "livestreaming",),
|
||||||
livestreaming_id,
|
(replace_id, &[zed_id], "replace")
|
||||||
&[zed_id],
|
|
||||||
"livestreaming",
|
|
||||||
ChannelRole::Admin
|
|
||||||
),
|
|
||||||
(replace_id, &[zed_id], "replace", ChannelRole::Admin)
|
|
||||||
],)
|
],)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -271,14 +251,19 @@ async fn test_channel_invites(db: &Arc<Database>) {
|
||||||
&[
|
&[
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: user_1.to_proto(),
|
user_id: user_1.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Admin.into(),
|
role: proto::ChannelRole::Admin.into(),
|
||||||
},
|
},
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: user_2.to_proto(),
|
user_id: user_2.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Member.into(),
|
role: proto::ChannelRole::Member.into(),
|
||||||
},
|
},
|
||||||
|
proto::ChannelMember {
|
||||||
|
user_id: user_3.to_proto(),
|
||||||
|
kind: proto::channel_member::Kind::Invitee.into(),
|
||||||
|
role: proto::ChannelRole::Admin.into(),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -420,13 +405,6 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Move to same parent should be a no-op
|
|
||||||
assert!(db
|
|
||||||
.move_channel(projects_id, Some(zed_id), user_id)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.is_none());
|
|
||||||
|
|
||||||
let result = db.get_channels_for_user(user_id).await.unwrap();
|
let result = db.get_channels_for_user(user_id).await.unwrap();
|
||||||
assert_channel_tree(
|
assert_channel_tree(
|
||||||
result.channels,
|
result.channels,
|
||||||
|
@ -437,20 +415,8 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Move the project channel to the root
|
|
||||||
db.move_channel(projects_id, None, user_id).await.unwrap();
|
|
||||||
let result = db.get_channels_for_user(user_id).await.unwrap();
|
|
||||||
assert_channel_tree(
|
|
||||||
result.channels,
|
|
||||||
&[
|
|
||||||
(zed_id, &[]),
|
|
||||||
(projects_id, &[]),
|
|
||||||
(livestreaming_id, &[projects_id]),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Can't move a channel into its ancestor
|
// Can't move a channel into its ancestor
|
||||||
db.move_channel(projects_id, Some(livestreaming_id), user_id)
|
db.move_channel(projects_id, livestreaming_id, user_id)
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
let result = db.get_channels_for_user(user_id).await.unwrap();
|
let result = db.get_channels_for_user(user_id).await.unwrap();
|
||||||
|
@ -458,8 +424,8 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
|
||||||
result.channels,
|
result.channels,
|
||||||
&[
|
&[
|
||||||
(zed_id, &[]),
|
(zed_id, &[]),
|
||||||
(projects_id, &[]),
|
(projects_id, &[zed_id]),
|
||||||
(livestreaming_id, &[projects_id]),
|
(livestreaming_id, &[zed_id, projects_id]),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -476,32 +442,39 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
let guest = new_test_user(db, "guest@example.com").await;
|
let guest = new_test_user(db, "guest@example.com").await;
|
||||||
|
|
||||||
let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
|
let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
|
||||||
let active_channel_id = db
|
let internal_channel_id = db
|
||||||
.create_sub_channel("active", zed_channel, admin)
|
.create_sub_channel("active", zed_channel, admin)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vim_channel_id = db
|
let public_channel_id = db
|
||||||
.create_sub_channel("vim", active_channel_id, admin)
|
.create_sub_channel("vim", zed_channel, admin)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
db.set_channel_visibility(vim_channel_id, crate::db::ChannelVisibility::Public, admin)
|
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
db.invite_channel_member(active_channel_id, member, admin, ChannelRole::Member)
|
db.set_channel_visibility(
|
||||||
|
public_channel_id,
|
||||||
|
crate::db::ChannelVisibility::Public,
|
||||||
|
admin,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
db.invite_channel_member(vim_channel_id, guest, admin, ChannelRole::Guest)
|
db.invite_channel_member(zed_channel, member, admin, ChannelRole::Member)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
db.invite_channel_member(zed_channel, guest, admin, ChannelRole::Guest)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
db.respond_to_channel_invite(active_channel_id, member, true)
|
db.respond_to_channel_invite(zed_channel, member, true)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
db.transaction(|tx| async move {
|
db.transaction(|tx| async move {
|
||||||
db.check_user_is_channel_participant(
|
db.check_user_is_channel_participant(
|
||||||
&db.get_channel_internal(vim_channel_id, &*tx).await?,
|
&db.get_channel_internal(public_channel_id, &*tx).await?,
|
||||||
admin,
|
admin,
|
||||||
&*tx,
|
&*tx,
|
||||||
)
|
)
|
||||||
|
@ -511,7 +484,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
db.transaction(|tx| async move {
|
db.transaction(|tx| async move {
|
||||||
db.check_user_is_channel_participant(
|
db.check_user_is_channel_participant(
|
||||||
&db.get_channel_internal(vim_channel_id, &*tx).await?,
|
&db.get_channel_internal(public_channel_id, &*tx).await?,
|
||||||
member,
|
member,
|
||||||
&*tx,
|
&*tx,
|
||||||
)
|
)
|
||||||
|
@ -521,7 +494,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut members = db
|
let mut members = db
|
||||||
.get_channel_participant_details(vim_channel_id, admin)
|
.get_channel_participant_details(public_channel_id, admin)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -532,12 +505,12 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
&[
|
&[
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: admin.to_proto(),
|
user_id: admin.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Admin.into(),
|
role: proto::ChannelRole::Admin.into(),
|
||||||
},
|
},
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: member.to_proto(),
|
user_id: member.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Member.into(),
|
role: proto::ChannelRole::Member.into(),
|
||||||
},
|
},
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
|
@ -548,13 +521,13 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
db.respond_to_channel_invite(vim_channel_id, guest, true)
|
db.respond_to_channel_invite(zed_channel, guest, true)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
db.transaction(|tx| async move {
|
db.transaction(|tx| async move {
|
||||||
db.check_user_is_channel_participant(
|
db.check_user_is_channel_participant(
|
||||||
&db.get_channel_internal(vim_channel_id, &*tx).await?,
|
&db.get_channel_internal(public_channel_id, &*tx).await?,
|
||||||
guest,
|
guest,
|
||||||
&*tx,
|
&*tx,
|
||||||
)
|
)
|
||||||
|
@ -564,23 +537,29 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let channels = db.get_channels_for_user(guest).await.unwrap().channels;
|
let channels = db.get_channels_for_user(guest).await.unwrap().channels;
|
||||||
assert_channel_tree(channels, &[(vim_channel_id, &[])]);
|
assert_channel_tree(
|
||||||
|
channels,
|
||||||
|
&[(zed_channel, &[]), (public_channel_id, &[zed_channel])],
|
||||||
|
);
|
||||||
let channels = db.get_channels_for_user(member).await.unwrap().channels;
|
let channels = db.get_channels_for_user(member).await.unwrap().channels;
|
||||||
assert_channel_tree(
|
assert_channel_tree(
|
||||||
channels,
|
channels,
|
||||||
&[
|
&[
|
||||||
(active_channel_id, &[]),
|
(zed_channel, &[]),
|
||||||
(vim_channel_id, &[active_channel_id]),
|
(internal_channel_id, &[zed_channel]),
|
||||||
|
(public_channel_id, &[zed_channel]),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
db.set_channel_member_role(vim_channel_id, admin, guest, ChannelRole::Banned)
|
db.set_channel_member_role(zed_channel, admin, guest, ChannelRole::Banned)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(db
|
assert!(db
|
||||||
.transaction(|tx| async move {
|
.transaction(|tx| async move {
|
||||||
db.check_user_is_channel_participant(
|
db.check_user_is_channel_participant(
|
||||||
&db.get_channel_internal(vim_channel_id, &*tx).await.unwrap(),
|
&db.get_channel_internal(public_channel_id, &*tx)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
guest,
|
guest,
|
||||||
&*tx,
|
&*tx,
|
||||||
)
|
)
|
||||||
|
@ -590,7 +569,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
let mut members = db
|
let mut members = db
|
||||||
.get_channel_participant_details(vim_channel_id, admin)
|
.get_channel_participant_details(public_channel_id, admin)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -601,12 +580,12 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
&[
|
&[
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: admin.to_proto(),
|
user_id: admin.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Admin.into(),
|
role: proto::ChannelRole::Admin.into(),
|
||||||
},
|
},
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: member.to_proto(),
|
user_id: member.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Member.into(),
|
role: proto::ChannelRole::Member.into(),
|
||||||
},
|
},
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
|
@ -617,11 +596,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
db.remove_channel_member(vim_channel_id, guest, admin)
|
db.remove_channel_member(zed_channel, guest, admin)
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -631,7 +606,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
|
|
||||||
// currently people invited to parent channels are not shown here
|
// currently people invited to parent channels are not shown here
|
||||||
let mut members = db
|
let mut members = db
|
||||||
.get_channel_participant_details(vim_channel_id, admin)
|
.get_channel_participant_details(public_channel_id, admin)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -642,14 +617,19 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
&[
|
&[
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: admin.to_proto(),
|
user_id: admin.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Admin.into(),
|
role: proto::ChannelRole::Admin.into(),
|
||||||
},
|
},
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: member.to_proto(),
|
user_id: member.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Member.into(),
|
role: proto::ChannelRole::Member.into(),
|
||||||
},
|
},
|
||||||
|
proto::ChannelMember {
|
||||||
|
user_id: guest.to_proto(),
|
||||||
|
kind: proto::channel_member::Kind::Invitee.into(),
|
||||||
|
role: proto::ChannelRole::Guest.into(),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -670,7 +650,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
assert!(db
|
assert!(db
|
||||||
.transaction(|tx| async move {
|
.transaction(|tx| async move {
|
||||||
db.check_user_is_channel_participant(
|
db.check_user_is_channel_participant(
|
||||||
&db.get_channel_internal(active_channel_id, &*tx)
|
&db.get_channel_internal(internal_channel_id, &*tx)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
guest,
|
guest,
|
||||||
|
@ -683,7 +663,9 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
|
|
||||||
db.transaction(|tx| async move {
|
db.transaction(|tx| async move {
|
||||||
db.check_user_is_channel_participant(
|
db.check_user_is_channel_participant(
|
||||||
&db.get_channel_internal(vim_channel_id, &*tx).await.unwrap(),
|
&db.get_channel_internal(public_channel_id, &*tx)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
guest,
|
guest,
|
||||||
&*tx,
|
&*tx,
|
||||||
)
|
)
|
||||||
|
@ -693,7 +675,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut members = db
|
let mut members = db
|
||||||
.get_channel_participant_details(vim_channel_id, admin)
|
.get_channel_participant_details(public_channel_id, admin)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -704,17 +686,17 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
&[
|
&[
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: admin.to_proto(),
|
user_id: admin.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Admin.into(),
|
role: proto::ChannelRole::Admin.into(),
|
||||||
},
|
},
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: member.to_proto(),
|
user_id: member.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Member.into(),
|
role: proto::ChannelRole::Member.into(),
|
||||||
},
|
},
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
user_id: guest.to_proto(),
|
user_id: guest.to_proto(),
|
||||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
kind: proto::channel_member::Kind::Member.into(),
|
||||||
role: proto::ChannelRole::Guest.into(),
|
role: proto::ChannelRole::Guest.into(),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -723,67 +705,10 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||||
let channels = db.get_channels_for_user(guest).await.unwrap().channels;
|
let channels = db.get_channels_for_user(guest).await.unwrap().channels;
|
||||||
assert_channel_tree(
|
assert_channel_tree(
|
||||||
channels,
|
channels,
|
||||||
&[(zed_channel, &[]), (vim_channel_id, &[zed_channel])],
|
&[(zed_channel, &[]), (public_channel_id, &[zed_channel])],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
test_both_dbs!(
|
|
||||||
test_user_joins_correct_channel,
|
|
||||||
test_user_joins_correct_channel_postgres,
|
|
||||||
test_user_joins_correct_channel_sqlite
|
|
||||||
);
|
|
||||||
|
|
||||||
async fn test_user_joins_correct_channel(db: &Arc<Database>) {
|
|
||||||
let admin = new_test_user(db, "admin@example.com").await;
|
|
||||||
|
|
||||||
let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
|
|
||||||
|
|
||||||
let active_channel = db
|
|
||||||
.create_sub_channel("active", zed_channel, admin)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let vim_channel = db
|
|
||||||
.create_sub_channel("vim", active_channel, admin)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let vim2_channel = db
|
|
||||||
.create_sub_channel("vim2", vim_channel, admin)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
db.set_channel_visibility(vim_channel, crate::db::ChannelVisibility::Public, admin)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
db.set_channel_visibility(vim2_channel, crate::db::ChannelVisibility::Public, admin)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let most_public = db
|
|
||||||
.transaction(|tx| async move {
|
|
||||||
Ok(db
|
|
||||||
.public_ancestors_including_self(
|
|
||||||
&db.get_channel_internal(vim_channel, &*tx).await.unwrap(),
|
|
||||||
&tx,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.first()
|
|
||||||
.cloned())
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.id;
|
|
||||||
|
|
||||||
assert_eq!(most_public, zed_channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
test_both_dbs!(
|
test_both_dbs!(
|
||||||
test_guest_access,
|
test_guest_access,
|
||||||
test_guest_access_postgres,
|
test_guest_access_postgres,
|
||||||
|
|
|
@ -15,7 +15,7 @@ test_both_dbs!(
|
||||||
|
|
||||||
async fn test_channel_message_retrieval(db: &Arc<Database>) {
|
async fn test_channel_message_retrieval(db: &Arc<Database>) {
|
||||||
let user = new_test_user(db, "user@example.com").await;
|
let user = new_test_user(db, "user@example.com").await;
|
||||||
let channel = db.create_channel("channel", None, user).await.unwrap();
|
let channel = db.create_channel("channel", None, user).await.unwrap().0;
|
||||||
|
|
||||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||||
db.join_channel_chat(channel.id, rpc::ConnectionId { owner_id, id: 0 }, user)
|
db.join_channel_chat(channel.id, rpc::ConnectionId { owner_id, id: 0 }, user)
|
||||||
|
@ -235,11 +235,10 @@ async fn test_unseen_channel_messages(db: &Arc<Database>) {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let second_message = db
|
let _ = db
|
||||||
.create_channel_message(channel_1, user, "1_2", &[], OffsetDateTime::now_utc(), 2)
|
.create_channel_message(channel_1, user, "1_2", &[], OffsetDateTime::now_utc(), 2)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.message_id;
|
|
||||||
|
|
||||||
let third_message = db
|
let third_message = db
|
||||||
.create_channel_message(channel_1, user, "1_3", &[], OffsetDateTime::now_utc(), 3)
|
.create_channel_message(channel_1, user, "1_3", &[], OffsetDateTime::now_utc(), 3)
|
||||||
|
@ -258,97 +257,27 @@ async fn test_unseen_channel_messages(db: &Arc<Database>) {
|
||||||
.message_id;
|
.message_id;
|
||||||
|
|
||||||
// Check that observer has new messages
|
// Check that observer has new messages
|
||||||
let unseen_messages = db
|
let latest_messages = db
|
||||||
.transaction(|tx| async move {
|
.transaction(|tx| async move {
|
||||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
db.latest_channel_messages(&[channel_1, channel_2], &*tx)
|
||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unseen_messages,
|
latest_messages,
|
||||||
[
|
[
|
||||||
rpc::proto::UnseenChannelMessage {
|
rpc::proto::ChannelMessageId {
|
||||||
channel_id: channel_1.to_proto(),
|
channel_id: channel_1.to_proto(),
|
||||||
message_id: third_message.to_proto(),
|
message_id: third_message.to_proto(),
|
||||||
},
|
},
|
||||||
rpc::proto::UnseenChannelMessage {
|
rpc::proto::ChannelMessageId {
|
||||||
channel_id: channel_2.to_proto(),
|
channel_id: channel_2.to_proto(),
|
||||||
message_id: fourth_message.to_proto(),
|
message_id: fourth_message.to_proto(),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Observe the second message
|
|
||||||
db.observe_channel_message(channel_1, observer, second_message)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Make sure the observer still has a new message
|
|
||||||
let unseen_messages = db
|
|
||||||
.transaction(|tx| async move {
|
|
||||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
unseen_messages,
|
|
||||||
[
|
|
||||||
rpc::proto::UnseenChannelMessage {
|
|
||||||
channel_id: channel_1.to_proto(),
|
|
||||||
message_id: third_message.to_proto(),
|
|
||||||
},
|
|
||||||
rpc::proto::UnseenChannelMessage {
|
|
||||||
channel_id: channel_2.to_proto(),
|
|
||||||
message_id: fourth_message.to_proto(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Observe the third message,
|
|
||||||
db.observe_channel_message(channel_1, observer, third_message)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Make sure the observer does not have a new method
|
|
||||||
let unseen_messages = db
|
|
||||||
.transaction(|tx| async move {
|
|
||||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
unseen_messages,
|
|
||||||
[rpc::proto::UnseenChannelMessage {
|
|
||||||
channel_id: channel_2.to_proto(),
|
|
||||||
message_id: fourth_message.to_proto(),
|
|
||||||
}]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Observe the second message again, should not regress our observed state
|
|
||||||
db.observe_channel_message(channel_1, observer, second_message)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Make sure the observer does not have a new message
|
|
||||||
let unseen_messages = db
|
|
||||||
.transaction(|tx| async move {
|
|
||||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
unseen_messages,
|
|
||||||
[rpc::proto::UnseenChannelMessage {
|
|
||||||
channel_id: channel_2.to_proto(),
|
|
||||||
message_id: fourth_message.to_proto(),
|
|
||||||
}]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test_both_dbs!(
|
test_both_dbs!(
|
||||||
|
@ -362,7 +291,12 @@ async fn test_channel_message_mentions(db: &Arc<Database>) {
|
||||||
let user_b = new_test_user(db, "user_b@example.com").await;
|
let user_b = new_test_user(db, "user_b@example.com").await;
|
||||||
let user_c = new_test_user(db, "user_c@example.com").await;
|
let user_c = new_test_user(db, "user_c@example.com").await;
|
||||||
|
|
||||||
let channel = db.create_channel("channel", None, user_a).await.unwrap().id;
|
let channel = db
|
||||||
|
.create_channel("channel", None, user_a)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.id;
|
||||||
db.invite_channel_member(channel, user_b, user_a, ChannelRole::Member)
|
db.invite_channel_member(channel, user_b, user_a, ChannelRole::Member)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -5,8 +5,7 @@ use crate::{
|
||||||
db::{
|
db::{
|
||||||
self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreatedChannelMessage, Database,
|
self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreatedChannelMessage, Database,
|
||||||
InviteMemberResult, MembershipUpdated, MessageId, NotificationId, ProjectId,
|
InviteMemberResult, MembershipUpdated, MessageId, NotificationId, ProjectId,
|
||||||
RemoveChannelMemberResult, RenameChannelResult, RespondToChannelInvite, RoomId, ServerId,
|
RemoveChannelMemberResult, RespondToChannelInvite, RoomId, ServerId, User, UserId,
|
||||||
SetChannelVisibilityResult, User, UserId,
|
|
||||||
},
|
},
|
||||||
executor::Executor,
|
executor::Executor,
|
||||||
AppState, Error, Result,
|
AppState, Error, Result,
|
||||||
|
@ -602,6 +601,7 @@ impl Server {
|
||||||
let mut pool = this.connection_pool.lock();
|
let mut pool = this.connection_pool.lock();
|
||||||
pool.add_connection(connection_id, user_id, user.admin);
|
pool.add_connection(connection_id, user_id, user.admin);
|
||||||
this.peer.send(connection_id, build_initial_contacts_update(contacts, &pool))?;
|
this.peer.send(connection_id, build_initial_contacts_update(contacts, &pool))?;
|
||||||
|
this.peer.send(connection_id, build_update_user_channels(&channels_for_user.channel_memberships))?;
|
||||||
this.peer.send(connection_id, build_channels_update(
|
this.peer.send(connection_id, build_channels_update(
|
||||||
channels_for_user,
|
channels_for_user,
|
||||||
channel_invites
|
channel_invites
|
||||||
|
@ -2300,7 +2300,7 @@ async fn create_channel(
|
||||||
let db = session.db().await;
|
let db = session.db().await;
|
||||||
|
|
||||||
let parent_id = request.parent_id.map(|id| ChannelId::from_proto(id));
|
let parent_id = request.parent_id.map(|id| ChannelId::from_proto(id));
|
||||||
let channel = db
|
let (channel, owner, channel_members) = db
|
||||||
.create_channel(&request.name, parent_id, session.user_id)
|
.create_channel(&request.name, parent_id, session.user_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -2309,20 +2309,30 @@ async fn create_channel(
|
||||||
parent_id: request.parent_id,
|
parent_id: request.parent_id,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let participants_to_update;
|
let connection_pool = session.connection_pool().await;
|
||||||
if let Some(parent) = parent_id {
|
if let Some(owner) = owner {
|
||||||
participants_to_update = db.new_participants_to_notify(parent).await?;
|
let update = proto::UpdateUserChannels {
|
||||||
} else {
|
channel_memberships: vec![proto::ChannelMembership {
|
||||||
participants_to_update = vec![];
|
channel_id: owner.channel_id.to_proto(),
|
||||||
|
role: owner.role.into(),
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
for connection_id in connection_pool.user_connection_ids(owner.user_id) {
|
||||||
|
session.peer.send(connection_id, update.clone())?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let connection_pool = session.connection_pool().await;
|
for channel_member in channel_members {
|
||||||
for (user_id, channels) in participants_to_update {
|
if !channel_member.role.can_see_channel(channel.visibility) {
|
||||||
let update = build_channels_update(channels, vec![]);
|
|
||||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
|
||||||
if user_id == session.user_id {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let update = proto::UpdateChannels {
|
||||||
|
channels: vec![channel.to_proto()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
for connection_id in connection_pool.user_connection_ids(channel_member.user_id) {
|
||||||
session.peer.send(connection_id, update.clone())?;
|
session.peer.send(connection_id, update.clone())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2439,7 +2449,9 @@ async fn remove_channel_member(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggle the channel between public and private
|
/// Toggle the channel between public and private.
|
||||||
|
/// Care is taken to maintain the invariant that public channels only descend from public channels,
|
||||||
|
/// (though members-only channels can appear at any point in the hierarchy).
|
||||||
async fn set_channel_visibility(
|
async fn set_channel_visibility(
|
||||||
request: proto::SetChannelVisibility,
|
request: proto::SetChannelVisibility,
|
||||||
response: Response<proto::SetChannelVisibility>,
|
response: Response<proto::SetChannelVisibility>,
|
||||||
|
@ -2449,27 +2461,25 @@ async fn set_channel_visibility(
|
||||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||||
let visibility = request.visibility().into();
|
let visibility = request.visibility().into();
|
||||||
|
|
||||||
let SetChannelVisibilityResult {
|
let (channel, channel_members) = db
|
||||||
participants_to_update,
|
|
||||||
participants_to_remove,
|
|
||||||
channels_to_remove,
|
|
||||||
} = db
|
|
||||||
.set_channel_visibility(channel_id, visibility, session.user_id)
|
.set_channel_visibility(channel_id, visibility, session.user_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let connection_pool = session.connection_pool().await;
|
let connection_pool = session.connection_pool().await;
|
||||||
for (user_id, channels) in participants_to_update {
|
for member in channel_members {
|
||||||
let update = build_channels_update(channels, vec![]);
|
let update = if member.role.can_see_channel(channel.visibility) {
|
||||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
proto::UpdateChannels {
|
||||||
session.peer.send(connection_id, update.clone())?;
|
channels: vec![channel.to_proto()],
|
||||||
}
|
|
||||||
}
|
|
||||||
for user_id in participants_to_remove {
|
|
||||||
let update = proto::UpdateChannels {
|
|
||||||
delete_channels: channels_to_remove.iter().map(|id| id.to_proto()).collect(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proto::UpdateChannels {
|
||||||
|
delete_channels: vec![channel.id.to_proto()],
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
|
||||||
|
for connection_id in connection_pool.user_connection_ids(member.user_id) {
|
||||||
session.peer.send(connection_id, update.clone())?;
|
session.peer.send(connection_id, update.clone())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2478,7 +2488,7 @@ async fn set_channel_visibility(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alter the role for a user in the channel
|
/// Alter the role for a user in the channel.
|
||||||
async fn set_channel_member_role(
|
async fn set_channel_member_role(
|
||||||
request: proto::SetChannelMemberRole,
|
request: proto::SetChannelMemberRole,
|
||||||
response: Response<proto::SetChannelMemberRole>,
|
response: Response<proto::SetChannelMemberRole>,
|
||||||
|
@ -2534,10 +2544,7 @@ async fn rename_channel(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
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 RenameChannelResult {
|
let (channel, channel_members) = db
|
||||||
channel,
|
|
||||||
participants_to_update,
|
|
||||||
} = db
|
|
||||||
.rename_channel(channel_id, session.user_id, &request.name)
|
.rename_channel(channel_id, session.user_id, &request.name)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -2546,13 +2553,15 @@ async fn rename_channel(
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let connection_pool = session.connection_pool().await;
|
let connection_pool = session.connection_pool().await;
|
||||||
for (user_id, channel) in participants_to_update {
|
for channel_member in channel_members {
|
||||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
if !channel_member.role.can_see_channel(channel.visibility) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let update = proto::UpdateChannels {
|
let update = proto::UpdateChannels {
|
||||||
channels: vec![channel.to_proto()],
|
channels: vec![channel.to_proto()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
for connection_id in connection_pool.user_connection_ids(channel_member.user_id) {
|
||||||
session.peer.send(connection_id, update.clone())?;
|
session.peer.send(connection_id, update.clone())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2567,59 +2576,39 @@ async fn move_channel(
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||||
let to = request.to.map(ChannelId::from_proto);
|
let to = ChannelId::from_proto(request.to);
|
||||||
|
|
||||||
let result = session
|
let (channels, channel_members) = session
|
||||||
.db()
|
.db()
|
||||||
.await
|
.await
|
||||||
.move_channel(channel_id, to, session.user_id)
|
.move_channel(channel_id, to, session.user_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(result) = result {
|
|
||||||
let participants_to_update: HashMap<_, _> = session
|
|
||||||
.db()
|
|
||||||
.await
|
|
||||||
.new_participants_to_notify(to.unwrap_or(channel_id))
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut moved_channels: HashSet<ChannelId> = HashSet::default();
|
|
||||||
for id in result.descendent_ids {
|
|
||||||
moved_channels.insert(id);
|
|
||||||
}
|
|
||||||
moved_channels.insert(channel_id);
|
|
||||||
|
|
||||||
let mut participants_to_remove: HashSet<UserId> = HashSet::default();
|
|
||||||
for participant in result.previous_participants {
|
|
||||||
if participant.kind == proto::channel_member::Kind::AncestorMember {
|
|
||||||
if !participants_to_update.contains_key(&participant.user_id) {
|
|
||||||
participants_to_remove.insert(participant.user_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let moved_channels: Vec<u64> = moved_channels.iter().map(|id| id.to_proto()).collect();
|
|
||||||
|
|
||||||
let connection_pool = session.connection_pool().await;
|
let connection_pool = session.connection_pool().await;
|
||||||
for (user_id, channels) in participants_to_update {
|
for member in channel_members {
|
||||||
let mut update = build_channels_update(channels, vec![]);
|
let channels = channels
|
||||||
update.delete_channels = moved_channels.clone();
|
.iter()
|
||||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
.filter_map(|channel| {
|
||||||
session.peer.send(connection_id, update.clone())?;
|
if member.role.can_see_channel(channel.visibility) {
|
||||||
|
Some(channel.to_proto())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if channels.is_empty() {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for user_id in participants_to_remove {
|
|
||||||
let update = proto::UpdateChannels {
|
let update = proto::UpdateChannels {
|
||||||
delete_channels: moved_channels.clone(),
|
channels,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
|
||||||
|
for connection_id in connection_pool.user_connection_ids(member.user_id) {
|
||||||
session.peer.send(connection_id, update.clone())?;
|
session.peer.send(connection_id, update.clone())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
response.send(Ack {})?;
|
response.send(Ack {})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -2851,7 +2840,7 @@ async fn update_channel_buffer(
|
||||||
session.peer.send(
|
session.peer.send(
|
||||||
peer_id.into(),
|
peer_id.into(),
|
||||||
proto::UpdateChannels {
|
proto::UpdateChannels {
|
||||||
unseen_channel_buffer_changes: vec![proto::UnseenChannelBufferChange {
|
latest_channel_buffer_versions: vec![proto::ChannelBufferVersion {
|
||||||
channel_id: channel_id.to_proto(),
|
channel_id: channel_id.to_proto(),
|
||||||
epoch: epoch as u64,
|
epoch: epoch as u64,
|
||||||
version: version.clone(),
|
version: version.clone(),
|
||||||
|
@ -3037,7 +3026,7 @@ async fn send_channel_message(
|
||||||
session.peer.send(
|
session.peer.send(
|
||||||
peer_id.into(),
|
peer_id.into(),
|
||||||
proto::UpdateChannels {
|
proto::UpdateChannels {
|
||||||
unseen_channel_messages: vec![proto::UnseenChannelMessage {
|
latest_channel_message_ids: vec![proto::ChannelMessageId {
|
||||||
channel_id: channel_id.to_proto(),
|
channel_id: channel_id.to_proto(),
|
||||||
message_id: message_id.to_proto(),
|
message_id: message_id.to_proto(),
|
||||||
}],
|
}],
|
||||||
|
@ -3292,6 +3281,18 @@ fn notify_membership_updated(
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
peer: &Peer,
|
peer: &Peer,
|
||||||
) {
|
) {
|
||||||
|
let user_channels_update = proto::UpdateUserChannels {
|
||||||
|
channel_memberships: result
|
||||||
|
.new_channels
|
||||||
|
.channel_memberships
|
||||||
|
.iter()
|
||||||
|
.map(|cm| proto::ChannelMembership {
|
||||||
|
channel_id: cm.channel_id.to_proto(),
|
||||||
|
role: cm.role.into(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let mut update = build_channels_update(result.new_channels, vec![]);
|
let mut update = build_channels_update(result.new_channels, vec![]);
|
||||||
update.delete_channels = result
|
update.delete_channels = result
|
||||||
.removed_channels
|
.removed_channels
|
||||||
|
@ -3301,10 +3302,27 @@ fn notify_membership_updated(
|
||||||
update.remove_channel_invitations = vec![result.channel_id.to_proto()];
|
update.remove_channel_invitations = vec![result.channel_id.to_proto()];
|
||||||
|
|
||||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
for connection_id in connection_pool.user_connection_ids(user_id) {
|
||||||
|
peer.send(connection_id, user_channels_update.clone())
|
||||||
|
.trace_err();
|
||||||
peer.send(connection_id, update.clone()).trace_err();
|
peer.send(connection_id, update.clone()).trace_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_update_user_channels(
|
||||||
|
memberships: &Vec<db::channel_member::Model>,
|
||||||
|
) -> proto::UpdateUserChannels {
|
||||||
|
proto::UpdateUserChannels {
|
||||||
|
channel_memberships: memberships
|
||||||
|
.iter()
|
||||||
|
.map(|m| proto::ChannelMembership {
|
||||||
|
channel_id: m.channel_id.to_proto(),
|
||||||
|
role: m.role.into(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn build_channels_update(
|
fn build_channels_update(
|
||||||
channels: ChannelsForUser,
|
channels: ChannelsForUser,
|
||||||
channel_invites: Vec<db::Channel>,
|
channel_invites: Vec<db::Channel>,
|
||||||
|
@ -3315,8 +3333,8 @@ fn build_channels_update(
|
||||||
update.channels.push(channel.to_proto());
|
update.channels.push(channel.to_proto());
|
||||||
}
|
}
|
||||||
|
|
||||||
update.unseen_channel_buffer_changes = channels.unseen_buffer_changes;
|
update.latest_channel_buffer_versions = channels.latest_buffer_versions;
|
||||||
update.unseen_channel_messages = channels.channel_messages;
|
update.latest_channel_message_ids = channels.latest_channel_messages;
|
||||||
|
|
||||||
for (channel_id, participants) in channels.channel_participants {
|
for (channel_id, participants) in channels.channel_participants {
|
||||||
update
|
update
|
||||||
|
|
|
@ -637,7 +637,6 @@ async fn test_channel_buffer_changes(
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.has_channel_buffer_changed(channel_id)
|
.has_channel_buffer_changed(channel_id)
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
assert!(has_buffer_changed);
|
assert!(has_buffer_changed);
|
||||||
|
|
||||||
|
@ -655,7 +654,6 @@ async fn test_channel_buffer_changes(
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.has_channel_buffer_changed(channel_id)
|
.has_channel_buffer_changed(channel_id)
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
assert!(!has_buffer_changed);
|
assert!(!has_buffer_changed);
|
||||||
|
|
||||||
|
@ -672,7 +670,6 @@ async fn test_channel_buffer_changes(
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.has_channel_buffer_changed(channel_id)
|
.has_channel_buffer_changed(channel_id)
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
assert!(!has_buffer_changed);
|
assert!(!has_buffer_changed);
|
||||||
|
|
||||||
|
@ -687,7 +684,6 @@ async fn test_channel_buffer_changes(
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.has_channel_buffer_changed(channel_id)
|
.has_channel_buffer_changed(channel_id)
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
assert!(!has_buffer_changed);
|
assert!(!has_buffer_changed);
|
||||||
|
|
||||||
|
@ -714,7 +710,6 @@ async fn test_channel_buffer_changes(
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.has_channel_buffer_changed(channel_id)
|
.has_channel_buffer_changed(channel_id)
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
assert!(has_buffer_changed);
|
assert!(has_buffer_changed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,6 +195,13 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
client_a
|
||||||
|
.channel_store()
|
||||||
|
.update(cx_a, |store, cx| {
|
||||||
|
store.set_channel_visibility(parent_channel_id, proto::ChannelVisibility::Public, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
client_a
|
client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |store, cx| {
|
.update(cx_a, |store, cx| {
|
||||||
|
|
|
@ -313,7 +313,6 @@ async fn test_channel_message_changes(
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.has_new_messages(channel_id)
|
.has_new_messages(channel_id)
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(b_has_messages);
|
assert!(b_has_messages);
|
||||||
|
@ -341,7 +340,6 @@ async fn test_channel_message_changes(
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.has_new_messages(channel_id)
|
.has_new_messages(channel_id)
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(!b_has_messages);
|
assert!(!b_has_messages);
|
||||||
|
@ -359,7 +357,6 @@ async fn test_channel_message_changes(
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.has_new_messages(channel_id)
|
.has_new_messages(channel_id)
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(!b_has_messages);
|
assert!(!b_has_messages);
|
||||||
|
@ -382,7 +379,6 @@ async fn test_channel_message_changes(
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.has_new_messages(channel_id)
|
.has_new_messages(channel_id)
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(b_has_messages);
|
assert!(b_has_messages);
|
||||||
|
@ -402,7 +398,6 @@ async fn test_channel_message_changes(
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.has_new_messages(channel_id)
|
.has_new_messages(channel_id)
|
||||||
.unwrap()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(b_has_messages);
|
assert!(b_has_messages);
|
||||||
|
|
|
@ -48,13 +48,11 @@ async fn test_core_channels(
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".into(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_b_id,
|
id: channel_b_id,
|
||||||
name: "channel-b".into(),
|
name: "channel-b".into(),
|
||||||
depth: 1,
|
depth: 1,
|
||||||
role: ChannelRole::Admin,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -94,7 +92,6 @@ async fn test_core_channels(
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".into(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Member,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -141,13 +138,11 @@ async fn test_core_channels(
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".into(),
|
name: "channel-a".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
depth: 0,
|
depth: 0,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_b_id,
|
id: channel_b_id,
|
||||||
name: "channel-b".into(),
|
name: "channel-b".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
depth: 1,
|
depth: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -169,19 +164,16 @@ async fn test_core_channels(
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".into(),
|
name: "channel-a".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
depth: 0,
|
depth: 0,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_b_id,
|
id: channel_b_id,
|
||||||
name: "channel-b".into(),
|
name: "channel-b".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
depth: 1,
|
depth: 1,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_c_id,
|
id: channel_c_id,
|
||||||
name: "channel-c".into(),
|
name: "channel-c".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
depth: 2,
|
depth: 2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -213,19 +205,16 @@ async fn test_core_channels(
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".into(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_b_id,
|
id: channel_b_id,
|
||||||
name: "channel-b".into(),
|
name: "channel-b".into(),
|
||||||
depth: 1,
|
depth: 1,
|
||||||
role: ChannelRole::Admin,
|
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_c_id,
|
id: channel_c_id,
|
||||||
name: "channel-c".into(),
|
name: "channel-c".into(),
|
||||||
depth: 2,
|
depth: 2,
|
||||||
role: ChannelRole::Admin,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -247,7 +236,6 @@ async fn test_core_channels(
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".into(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
assert_channels(
|
assert_channels(
|
||||||
|
@ -257,7 +245,6 @@ async fn test_core_channels(
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".into(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -280,7 +267,6 @@ async fn test_core_channels(
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".into(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -311,7 +297,6 @@ async fn test_core_channels(
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a-renamed".into(),
|
name: "channel-a-renamed".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -420,7 +405,6 @@ async fn test_channel_room(
|
||||||
id: zed_id,
|
id: zed_id,
|
||||||
name: "zed".into(),
|
name: "zed".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Member,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
cx_b.read(|cx| {
|
cx_b.read(|cx| {
|
||||||
|
@ -681,7 +665,6 @@ async fn test_permissions_update_while_invited(
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: rust_id,
|
id: rust_id,
|
||||||
name: "rust".into(),
|
name: "rust".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
assert_channels(client_b.channel_store(), cx_b, &[]);
|
assert_channels(client_b.channel_store(), cx_b, &[]);
|
||||||
|
@ -709,7 +692,6 @@ async fn test_permissions_update_while_invited(
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: rust_id,
|
id: rust_id,
|
||||||
name: "rust".into(),
|
name: "rust".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
assert_channels(client_b.channel_store(), cx_b, &[]);
|
assert_channels(client_b.channel_store(), cx_b, &[]);
|
||||||
|
@ -748,7 +730,6 @@ async fn test_channel_rename(
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: rust_id,
|
id: rust_id,
|
||||||
name: "rust-archive".into(),
|
name: "rust-archive".into(),
|
||||||
role: ChannelRole::Admin,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -760,7 +741,6 @@ async fn test_channel_rename(
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: rust_id,
|
id: rust_id,
|
||||||
name: "rust-archive".into(),
|
name: "rust-archive".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -889,7 +869,6 @@ async fn test_lost_channel_creation(
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: channel_id,
|
id: channel_id,
|
||||||
name: "x".into(),
|
name: "x".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -913,13 +892,11 @@ async fn test_lost_channel_creation(
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: channel_id,
|
id: channel_id,
|
||||||
name: "x".into(),
|
name: "x".into(),
|
||||||
role: ChannelRole::Admin,
|
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 1,
|
depth: 1,
|
||||||
id: subchannel_id,
|
id: subchannel_id,
|
||||||
name: "subchannel".into(),
|
name: "subchannel".into(),
|
||||||
role: ChannelRole::Admin,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -944,13 +921,11 @@ async fn test_lost_channel_creation(
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: channel_id,
|
id: channel_id,
|
||||||
name: "x".into(),
|
name: "x".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 1,
|
depth: 1,
|
||||||
id: subchannel_id,
|
id: subchannel_id,
|
||||||
name: "subchannel".into(),
|
name: "subchannel".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1035,7 +1010,7 @@ async fn test_channel_link_notifications(
|
||||||
let vim_channel = client_a
|
let vim_channel = client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel_store, cx| {
|
.update(cx_a, |channel_store, cx| {
|
||||||
channel_store.create_channel("vim", None, cx)
|
channel_store.create_channel("vim", Some(zed_channel), cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1048,26 +1023,16 @@ async fn test_channel_link_notifications(
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
client_a
|
|
||||||
.channel_store()
|
|
||||||
.update(cx_a, |channel_store, cx| {
|
|
||||||
channel_store.move_channel(vim_channel, Some(active_channel), cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
executor.run_until_parked();
|
|
||||||
|
|
||||||
// the new channel shows for b and c
|
// the new channel shows for b and c
|
||||||
assert_channels_list_shape(
|
assert_channels_list_shape(
|
||||||
client_a.channel_store(),
|
client_a.channel_store(),
|
||||||
cx_a,
|
cx_a,
|
||||||
&[(zed_channel, 0), (active_channel, 1), (vim_channel, 2)],
|
&[(zed_channel, 0), (active_channel, 1), (vim_channel, 1)],
|
||||||
);
|
);
|
||||||
assert_channels_list_shape(
|
assert_channels_list_shape(
|
||||||
client_b.channel_store(),
|
client_b.channel_store(),
|
||||||
cx_b,
|
cx_b,
|
||||||
&[(zed_channel, 0), (active_channel, 1), (vim_channel, 2)],
|
&[(zed_channel, 0), (active_channel, 1), (vim_channel, 1)],
|
||||||
);
|
);
|
||||||
assert_channels_list_shape(
|
assert_channels_list_shape(
|
||||||
client_c.channel_store(),
|
client_c.channel_store(),
|
||||||
|
@ -1078,7 +1043,7 @@ async fn test_channel_link_notifications(
|
||||||
let helix_channel = client_a
|
let helix_channel = client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel_store, cx| {
|
.update(cx_a, |channel_store, cx| {
|
||||||
channel_store.create_channel("helix", None, cx)
|
channel_store.create_channel("helix", Some(zed_channel), cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1086,7 +1051,7 @@ async fn test_channel_link_notifications(
|
||||||
client_a
|
client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel_store, cx| {
|
.update(cx_a, |channel_store, cx| {
|
||||||
channel_store.move_channel(helix_channel, Some(vim_channel), cx)
|
channel_store.move_channel(helix_channel, vim_channel, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1102,6 +1067,7 @@ async fn test_channel_link_notifications(
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
cx_a.run_until_parked();
|
||||||
|
|
||||||
// the new channel shows for b and c
|
// the new channel shows for b and c
|
||||||
assert_channels_list_shape(
|
assert_channels_list_shape(
|
||||||
|
@ -1110,8 +1076,8 @@ async fn test_channel_link_notifications(
|
||||||
&[
|
&[
|
||||||
(zed_channel, 0),
|
(zed_channel, 0),
|
||||||
(active_channel, 1),
|
(active_channel, 1),
|
||||||
(vim_channel, 2),
|
(vim_channel, 1),
|
||||||
(helix_channel, 3),
|
(helix_channel, 2),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
assert_channels_list_shape(
|
assert_channels_list_shape(
|
||||||
|
@ -1119,41 +1085,6 @@ async fn test_channel_link_notifications(
|
||||||
cx_c,
|
cx_c,
|
||||||
&[(zed_channel, 0), (vim_channel, 1), (helix_channel, 2)],
|
&[(zed_channel, 0), (vim_channel, 1), (helix_channel, 2)],
|
||||||
);
|
);
|
||||||
|
|
||||||
client_a
|
|
||||||
.channel_store()
|
|
||||||
.update(cx_a, |channel_store, cx| {
|
|
||||||
channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Members, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
executor.run_until_parked();
|
|
||||||
|
|
||||||
// the members-only channel is still shown for c, but hidden for b
|
|
||||||
assert_channels_list_shape(
|
|
||||||
client_b.channel_store(),
|
|
||||||
cx_b,
|
|
||||||
&[
|
|
||||||
(zed_channel, 0),
|
|
||||||
(active_channel, 1),
|
|
||||||
(vim_channel, 2),
|
|
||||||
(helix_channel, 3),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
cx_b.read(|cx| {
|
|
||||||
client_b.channel_store().read_with(cx, |channel_store, _| {
|
|
||||||
assert_eq!(
|
|
||||||
channel_store
|
|
||||||
.channel_for_id(vim_channel)
|
|
||||||
.unwrap()
|
|
||||||
.visibility,
|
|
||||||
proto::ChannelVisibility::Members
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_channels_list_shape(client_c.channel_store(), cx_c, &[(zed_channel, 0)]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -1170,24 +1101,20 @@ async fn test_channel_membership_notifications(
|
||||||
|
|
||||||
let channels = server
|
let channels = server
|
||||||
.make_channel_tree(
|
.make_channel_tree(
|
||||||
&[
|
&[("zed", None), ("vim", Some("zed")), ("opensource", None)],
|
||||||
("zed", None),
|
|
||||||
("active", Some("zed")),
|
|
||||||
("vim", Some("active")),
|
|
||||||
],
|
|
||||||
(&client_a, cx_a),
|
(&client_a, cx_a),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let zed_channel = channels[0];
|
let zed_channel = channels[0];
|
||||||
let _active_channel = channels[1];
|
let vim_channel = channels[1];
|
||||||
let vim_channel = channels[2];
|
let opensource_channel = channels[2];
|
||||||
|
|
||||||
try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
|
try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
|
||||||
[
|
[
|
||||||
channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
|
channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
|
||||||
channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx),
|
channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx),
|
||||||
channel_store.invite_member(vim_channel, user_b, proto::ChannelRole::Member, cx),
|
channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Admin, cx),
|
||||||
channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Guest, cx),
|
channel_store.invite_member(opensource_channel, user_b, proto::ChannelRole::Member, cx),
|
||||||
]
|
]
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
|
@ -1203,14 +1130,6 @@ async fn test_channel_membership_notifications(
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
client_b
|
|
||||||
.channel_store()
|
|
||||||
.update(cx_b, |channel_store, cx| {
|
|
||||||
channel_store.respond_to_channel_invite(vim_channel, true, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
// we have an admin (a), and a guest (b) with access to all of zed, and membership in vim.
|
// we have an admin (a), and a guest (b) with access to all of zed, and membership in vim.
|
||||||
|
@ -1222,45 +1141,42 @@ async fn test_channel_membership_notifications(
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: zed_channel,
|
id: zed_channel,
|
||||||
name: "zed".into(),
|
name: "zed".into(),
|
||||||
role: ChannelRole::Guest,
|
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 1,
|
depth: 1,
|
||||||
id: vim_channel,
|
id: vim_channel,
|
||||||
name: "vim".into(),
|
name: "vim".into(),
|
||||||
role: ChannelRole::Member,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
client_a
|
client_b.channel_store().update(cx_b, |channel_store, _| {
|
||||||
|
channel_store.is_channel_admin(zed_channel)
|
||||||
|
});
|
||||||
|
|
||||||
|
client_b
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel_store, cx| {
|
.update(cx_b, |channel_store, cx| {
|
||||||
channel_store.remove_member(vim_channel, user_b, cx)
|
channel_store.respond_to_channel_invite(opensource_channel, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
executor.run_until_parked();
|
cx_a.run_until_parked();
|
||||||
|
|
||||||
assert_channels(
|
client_a
|
||||||
client_b.channel_store(),
|
.channel_store()
|
||||||
cx_b,
|
.update(cx_a, |channel_store, cx| {
|
||||||
&[
|
channel_store.set_member_role(opensource_channel, user_b, ChannelRole::Admin, cx)
|
||||||
ExpectedChannel {
|
})
|
||||||
depth: 0,
|
.await
|
||||||
id: zed_channel,
|
.unwrap();
|
||||||
name: "zed".into(),
|
|
||||||
role: ChannelRole::Guest,
|
cx_a.run_until_parked();
|
||||||
},
|
|
||||||
ExpectedChannel {
|
client_b.channel_store().update(cx_b, |channel_store, _| {
|
||||||
depth: 1,
|
channel_store.is_channel_admin(opensource_channel)
|
||||||
id: vim_channel,
|
});
|
||||||
name: "vim".into(),
|
|
||||||
role: ChannelRole::Guest,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -1329,25 +1245,6 @@ async fn test_guest_access(
|
||||||
assert_eq!(participants.len(), 1);
|
assert_eq!(participants.len(), 1);
|
||||||
assert_eq!(participants[0].id, client_b.user_id().unwrap());
|
assert_eq!(participants[0].id, client_b.user_id().unwrap());
|
||||||
});
|
});
|
||||||
|
|
||||||
client_a
|
|
||||||
.channel_store()
|
|
||||||
.update(cx_a, |channel_store, cx| {
|
|
||||||
channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Members, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
executor.run_until_parked();
|
|
||||||
|
|
||||||
assert_channels_list_shape(client_b.channel_store(), cx_b, &[]);
|
|
||||||
|
|
||||||
active_call_b
|
|
||||||
.update(cx_b, |call, cx| call.join_channel(channel_b, cx))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
executor.run_until_parked();
|
|
||||||
assert_channels_list_shape(client_b.channel_store(), cx_b, &[(channel_b, 0)]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -1451,7 +1348,7 @@ async fn test_channel_moving(
|
||||||
client_a
|
client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel_store, cx| {
|
.update(cx_a, |channel_store, cx| {
|
||||||
channel_store.move_channel(channel_d_id, Some(channel_b_id), cx)
|
channel_store.move_channel(channel_d_id, channel_b_id, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1476,7 +1373,6 @@ struct ExpectedChannel {
|
||||||
depth: usize,
|
depth: usize,
|
||||||
id: ChannelId,
|
id: ChannelId,
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
role: ChannelRole,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
@ -1494,7 +1390,6 @@ fn assert_channel_invitations(
|
||||||
depth: 0,
|
depth: 0,
|
||||||
name: channel.name.clone(),
|
name: channel.name.clone(),
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
role: channel.role,
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
|
@ -1516,7 +1411,6 @@ fn assert_channels(
|
||||||
depth,
|
depth,
|
||||||
name: channel.name.clone().into(),
|
name: channel.name.clone().into(),
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
role: channel.role,
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
|
|
|
@ -3884,6 +3884,7 @@ async fn test_collaborating_with_diagnostics(
|
||||||
|
|
||||||
// Join project as client C and observe the diagnostics.
|
// Join project as client C and observe the diagnostics.
|
||||||
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
||||||
|
executor.run_until_parked();
|
||||||
let project_c_diagnostic_summaries =
|
let project_c_diagnostic_summaries =
|
||||||
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
|
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
|
||||||
project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
|
project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
|
||||||
|
|
|
@ -125,6 +125,7 @@ impl TestServer {
|
||||||
let channel_id = server
|
let channel_id = server
|
||||||
.make_channel("a", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
.make_channel("a", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
||||||
.await;
|
.await;
|
||||||
|
cx_a.run_until_parked();
|
||||||
|
|
||||||
(client_a, client_b, channel_id)
|
(client_a, client_b, channel_id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,7 @@ impl ChannelView {
|
||||||
} else {
|
} else {
|
||||||
self.channel_store.update(cx, |store, cx| {
|
self.channel_store.update(cx, |store, cx| {
|
||||||
let channel_buffer = self.channel_buffer.read(cx);
|
let channel_buffer = self.channel_buffer.read(cx);
|
||||||
store.notes_changed(
|
store.update_latest_notes_version(
|
||||||
channel_buffer.channel_id,
|
channel_buffer.channel_id,
|
||||||
channel_buffer.epoch(),
|
channel_buffer.epoch(),
|
||||||
&channel_buffer.buffer().read(cx).version(),
|
&channel_buffer.buffer().read(cx).version(),
|
||||||
|
|
|
@ -266,7 +266,7 @@ impl ChatPanel {
|
||||||
} => {
|
} => {
|
||||||
if !self.active {
|
if !self.active {
|
||||||
self.channel_store.update(cx, |store, cx| {
|
self.channel_store.update(cx, |store, cx| {
|
||||||
store.new_message(*channel_id, *message_id, cx)
|
store.update_latest_message_id(*channel_id, *message_id, cx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ use gpui::{
|
||||||
use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev};
|
use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev};
|
||||||
use project::{Fs, Project};
|
use project::{Fs, Project};
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, PeerId},
|
proto::{self, ChannelVisibility, PeerId},
|
||||||
ErrorCode, ErrorExt,
|
ErrorCode, ErrorExt,
|
||||||
};
|
};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
@ -1134,13 +1134,6 @@ impl CollabPanel {
|
||||||
"Rename",
|
"Rename",
|
||||||
Some(Box::new(SecondaryConfirm)),
|
Some(Box::new(SecondaryConfirm)),
|
||||||
cx.handler_for(&this, move |this, cx| this.rename_channel(channel_id, cx)),
|
cx.handler_for(&this, move |this, cx| this.rename_channel(channel_id, cx)),
|
||||||
)
|
|
||||||
.entry(
|
|
||||||
"Move this channel",
|
|
||||||
None,
|
|
||||||
cx.handler_for(&this, move |this, cx| {
|
|
||||||
this.start_move_channel(channel_id, cx)
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(channel_name) = clipboard_channel_name {
|
if let Some(channel_name) = clipboard_channel_name {
|
||||||
|
@ -1153,19 +1146,48 @@ impl CollabPanel {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
context_menu = context_menu
|
if self.channel_store.read(cx).is_root_channel(channel_id) {
|
||||||
.separator()
|
context_menu = context_menu.separator().entry(
|
||||||
.entry(
|
|
||||||
"Invite Members",
|
|
||||||
None,
|
|
||||||
cx.handler_for(&this, move |this, cx| this.invite_members(channel_id, cx)),
|
|
||||||
)
|
|
||||||
.entry(
|
|
||||||
"Manage Members",
|
"Manage Members",
|
||||||
None,
|
None,
|
||||||
cx.handler_for(&this, move |this, cx| this.manage_members(channel_id, cx)),
|
cx.handler_for(&this, move |this, cx| this.manage_members(channel_id, cx)),
|
||||||
)
|
)
|
||||||
.entry(
|
} else {
|
||||||
|
context_menu = context_menu.entry(
|
||||||
|
"Move this channel",
|
||||||
|
None,
|
||||||
|
cx.handler_for(&this, move |this, cx| {
|
||||||
|
this.start_move_channel(channel_id, cx)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if self.channel_store.read(cx).is_public_channel(channel_id) {
|
||||||
|
context_menu = context_menu.separator().entry(
|
||||||
|
"Make Channel Private",
|
||||||
|
None,
|
||||||
|
cx.handler_for(&this, move |this, cx| {
|
||||||
|
this.set_channel_visibility(
|
||||||
|
channel_id,
|
||||||
|
ChannelVisibility::Members,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
context_menu = context_menu.separator().entry(
|
||||||
|
"Make Channel Public",
|
||||||
|
None,
|
||||||
|
cx.handler_for(&this, move |this, cx| {
|
||||||
|
this.set_channel_visibility(
|
||||||
|
channel_id,
|
||||||
|
ChannelVisibility::Public,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context_menu = context_menu.entry(
|
||||||
"Delete",
|
"Delete",
|
||||||
None,
|
None,
|
||||||
cx.handler_for(&this, move |this, cx| this.remove_channel(channel_id, cx)),
|
cx.handler_for(&this, move |this, cx| this.remove_channel(channel_id, cx)),
|
||||||
|
@ -1490,10 +1512,6 @@ impl CollabPanel {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invite_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
|
||||||
self.show_channel_modal(channel_id, channel_modal::Mode::InviteMembers, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
fn manage_members(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
|
self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
|
||||||
}
|
}
|
||||||
|
@ -1530,6 +1548,27 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_channel_visibility(
|
||||||
|
&mut self,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
visibility: ChannelVisibility,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
self.channel_store
|
||||||
|
.update(cx, |channel_store, cx| {
|
||||||
|
channel_store.set_channel_visibility(channel_id, visibility, cx)
|
||||||
|
})
|
||||||
|
.detach_and_prompt_err("Failed to set channel visibility", cx, |e, _| match e.error_code() {
|
||||||
|
ErrorCode::BadPublicNesting =>
|
||||||
|
if e.error_tag("direction") == Some("parent") {
|
||||||
|
Some("To make a channel public, its parent channel must be public.".to_string())
|
||||||
|
} else {
|
||||||
|
Some("To make a channel private, all of its subchannels must be private.".to_string())
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn start_move_channel(&mut self, channel_id: ChannelId, _cx: &mut ViewContext<Self>) {
|
fn start_move_channel(&mut self, channel_id: ChannelId, _cx: &mut ViewContext<Self>) {
|
||||||
self.channel_clipboard = Some(ChannelMoveClipboard { channel_id });
|
self.channel_clipboard = Some(ChannelMoveClipboard { channel_id });
|
||||||
}
|
}
|
||||||
|
@ -1546,12 +1585,25 @@ impl CollabPanel {
|
||||||
cx: &mut ViewContext<CollabPanel>,
|
cx: &mut ViewContext<CollabPanel>,
|
||||||
) {
|
) {
|
||||||
if let Some(clipboard) = self.channel_clipboard.take() {
|
if let Some(clipboard) = self.channel_clipboard.take() {
|
||||||
|
self.move_channel(clipboard.channel_id, to_channel_id, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_channel(&self, channel_id: ChannelId, to: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
self.channel_store
|
self.channel_store
|
||||||
.update(cx, |channel_store, cx| {
|
.update(cx, |channel_store, cx| {
|
||||||
channel_store.move_channel(clipboard.channel_id, Some(to_channel_id), cx)
|
channel_store.move_channel(channel_id, to, cx)
|
||||||
})
|
})
|
||||||
.detach_and_prompt_err("Failed to move channel", cx, |_, _| None)
|
.detach_and_prompt_err("Failed to move channel", cx, |e, _| match e.error_code() {
|
||||||
|
ErrorCode::BadPublicNesting => {
|
||||||
|
Some("Public channels must have public parents".into())
|
||||||
}
|
}
|
||||||
|
ErrorCode::CircularNesting => Some("You cannot move a channel into itself".into()),
|
||||||
|
ErrorCode::WrongMoveTarget => {
|
||||||
|
Some("You cannot move a channel into a different root channel".into())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_channel_notes(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
fn open_channel_notes(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -1980,32 +2032,19 @@ impl CollabPanel {
|
||||||
| Section::Offline => true,
|
| Section::Offline => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
h_flex()
|
h_flex().w_full().group("section-header").child(
|
||||||
.w_full()
|
|
||||||
.group("section-header")
|
|
||||||
.child(
|
|
||||||
ListHeader::new(text)
|
ListHeader::new(text)
|
||||||
.when(can_collapse, |header| {
|
.when(can_collapse, |header| {
|
||||||
header.toggle(Some(!is_collapsed)).on_toggle(cx.listener(
|
header
|
||||||
move |this, _, cx| {
|
.toggle(Some(!is_collapsed))
|
||||||
|
.on_toggle(cx.listener(move |this, _, cx| {
|
||||||
this.toggle_section_expanded(section, cx);
|
this.toggle_section_expanded(section, cx);
|
||||||
},
|
}))
|
||||||
))
|
|
||||||
})
|
})
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.end_slot::<AnyElement>(button)
|
.end_slot::<AnyElement>(button)
|
||||||
.selected(is_selected),
|
.selected(is_selected),
|
||||||
)
|
)
|
||||||
.when(section == Section::Channels, |el| {
|
|
||||||
el.drag_over::<Channel>(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
|
||||||
.on_drop(cx.listener(move |this, dragged_channel: &Channel, cx| {
|
|
||||||
this.channel_store
|
|
||||||
.update(cx, |channel_store, cx| {
|
|
||||||
channel_store.move_channel(dragged_channel.id, None, cx)
|
|
||||||
})
|
|
||||||
.detach_and_prompt_err("Failed to move channel", cx, |_, _| None)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_contact(
|
fn render_contact(
|
||||||
|
@ -2219,17 +2258,16 @@ impl CollabPanel {
|
||||||
Some(call_channel == channel_id)
|
Some(call_channel == channel_id)
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
let is_public = self
|
let channel_store = self.channel_store.read(cx);
|
||||||
.channel_store
|
let is_public = channel_store
|
||||||
.read(cx)
|
|
||||||
.channel_for_id(channel_id)
|
.channel_for_id(channel_id)
|
||||||
.map(|channel| channel.visibility)
|
.map(|channel| channel.visibility)
|
||||||
== Some(proto::ChannelVisibility::Public);
|
== Some(proto::ChannelVisibility::Public);
|
||||||
let disclosed =
|
let disclosed =
|
||||||
has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
|
has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
|
||||||
|
|
||||||
let has_messages_notification = channel.unseen_message_id.is_some();
|
let has_messages_notification = channel_store.has_new_messages(channel_id);
|
||||||
let has_notes_notification = channel.unseen_note_version.is_some();
|
let has_notes_notification = channel_store.has_channel_buffer_changed(channel_id);
|
||||||
|
|
||||||
const FACEPILE_LIMIT: usize = 3;
|
const FACEPILE_LIMIT: usize = 3;
|
||||||
let participants = self.channel_store.read(cx).channel_participants(channel_id);
|
let participants = self.channel_store.read(cx).channel_participants(channel_id);
|
||||||
|
@ -2260,6 +2298,7 @@ impl CollabPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
let width = self.width.unwrap_or(px(240.));
|
let width = self.width.unwrap_or(px(240.));
|
||||||
|
let root_id = channel.root_id();
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.h_6()
|
.h_6()
|
||||||
|
@ -2267,19 +2306,28 @@ impl CollabPanel {
|
||||||
.group("")
|
.group("")
|
||||||
.flex()
|
.flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.on_drag(channel.clone(), move |channel, cx| {
|
.when(!channel.is_root_channel(), |el| {
|
||||||
|
el.on_drag(channel.clone(), move |channel, cx| {
|
||||||
cx.new_view(|_| DraggedChannelView {
|
cx.new_view(|_| DraggedChannelView {
|
||||||
channel: channel.clone(),
|
channel: channel.clone(),
|
||||||
width,
|
width,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.drag_over::<Channel>(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
|
||||||
.on_drop(cx.listener(move |this, dragged_channel: &Channel, cx| {
|
|
||||||
this.channel_store
|
|
||||||
.update(cx, |channel_store, cx| {
|
|
||||||
channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
|
|
||||||
})
|
})
|
||||||
.detach_and_prompt_err("Failed to move channel", cx, |_, _| None)
|
.drag_over::<Channel>({
|
||||||
|
move |style, dragged_channel: &Channel, cx| {
|
||||||
|
if dragged_channel.root_id() == root_id {
|
||||||
|
style.bg(cx.theme().colors().ghost_element_hover)
|
||||||
|
} else {
|
||||||
|
style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_drop(cx.listener(move |this, dragged_channel: &Channel, cx| {
|
||||||
|
if dragged_channel.root_id() != root_id {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.move_channel(dragged_channel.id, channel_id, cx);
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(
|
||||||
ListItem::new(channel_id as usize)
|
ListItem::new(channel_id as usize)
|
||||||
|
|
|
@ -10,7 +10,6 @@ use gpui::{
|
||||||
WeakView,
|
WeakView,
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use rpc::proto::channel_member;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{prelude::*, Avatar, Checkbox, ContextMenu, ListItem, ListItemSpacing};
|
use ui::{prelude::*, Avatar, Checkbox, ContextMenu, ListItem, ListItemSpacing};
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
|
@ -359,10 +358,8 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||||
Some(proto::channel_member::Kind::Invitee) => {
|
Some(proto::channel_member::Kind::Invitee) => {
|
||||||
self.remove_member(selected_user.id, cx);
|
self.remove_member(selected_user.id, cx);
|
||||||
}
|
}
|
||||||
Some(proto::channel_member::Kind::AncestorMember) | None => {
|
|
||||||
self.invite_member(selected_user, cx)
|
|
||||||
}
|
|
||||||
Some(proto::channel_member::Kind::Member) => {}
|
Some(proto::channel_member::Kind::Member) => {}
|
||||||
|
None => self.invite_member(selected_user, cx),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -402,10 +399,6 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||||
.children(
|
.children(
|
||||||
if request_status == Some(proto::channel_member::Kind::Invitee) {
|
if request_status == Some(proto::channel_member::Kind::Invitee) {
|
||||||
Some(Label::new("Invited"))
|
Some(Label::new("Invited"))
|
||||||
} else if membership.map(|m| m.kind)
|
|
||||||
== Some(channel_member::Kind::AncestorMember)
|
|
||||||
{
|
|
||||||
Some(Label::new("Parent"))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
@ -563,16 +556,9 @@ impl ChannelModalDelegate {
|
||||||
let Some(membership) = self.member_at_index(ix) else {
|
let Some(membership) = self.member_at_index(ix) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if membership.kind == proto::channel_member::Kind::AncestorMember {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let user_id = membership.user.id;
|
let user_id = membership.user.id;
|
||||||
let picker = cx.view().clone();
|
let picker = cx.view().clone();
|
||||||
let context_menu = ContextMenu::build(cx, |mut menu, _cx| {
|
let context_menu = ContextMenu::build(cx, |mut menu, _cx| {
|
||||||
if membership.kind == channel_member::Kind::AncestorMember {
|
|
||||||
return menu.entry("Inherited membership", None, |_| {});
|
|
||||||
};
|
|
||||||
|
|
||||||
let role = membership.role;
|
let role = membership.role;
|
||||||
|
|
||||||
if role == ChannelRole::Admin || role == ChannelRole::Member {
|
if role == ChannelRole::Admin || role == ChannelRole::Member {
|
||||||
|
|
|
@ -782,10 +782,20 @@ pub trait InteractiveElement: Sized {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the given style when the given data type is dragged over this element
|
/// Apply the given style when the given data type is dragged over this element
|
||||||
fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
|
fn drag_over<S: 'static>(
|
||||||
self.interactivity()
|
mut self,
|
||||||
.drag_over_styles
|
f: impl 'static + Fn(StyleRefinement, &S, &WindowContext) -> StyleRefinement,
|
||||||
.push((TypeId::of::<S>(), f(StyleRefinement::default())));
|
) -> Self {
|
||||||
|
self.interactivity().drag_over_styles.push((
|
||||||
|
TypeId::of::<S>(),
|
||||||
|
Box::new(move |currently_dragged: &dyn Any, cx| {
|
||||||
|
f(
|
||||||
|
StyleRefinement::default(),
|
||||||
|
currently_dragged.downcast_ref::<S>().unwrap(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1174,7 +1184,10 @@ pub struct Interactivity {
|
||||||
pub(crate) group_hover_style: Option<GroupStyle>,
|
pub(crate) group_hover_style: Option<GroupStyle>,
|
||||||
pub(crate) active_style: Option<Box<StyleRefinement>>,
|
pub(crate) active_style: Option<Box<StyleRefinement>>,
|
||||||
pub(crate) group_active_style: Option<GroupStyle>,
|
pub(crate) group_active_style: Option<GroupStyle>,
|
||||||
pub(crate) drag_over_styles: Vec<(TypeId, StyleRefinement)>,
|
pub(crate) drag_over_styles: Vec<(
|
||||||
|
TypeId,
|
||||||
|
Box<dyn Fn(&dyn Any, &mut WindowContext) -> StyleRefinement>,
|
||||||
|
)>,
|
||||||
pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
|
pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
|
||||||
pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
|
pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
|
||||||
pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
|
pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
|
||||||
|
@ -1980,7 +1993,7 @@ impl Interactivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (state_type, drag_over_style) in &self.drag_over_styles {
|
for (state_type, build_drag_over_style) in &self.drag_over_styles {
|
||||||
if *state_type == drag.value.as_ref().type_id()
|
if *state_type == drag.value.as_ref().type_id()
|
||||||
&& bounds
|
&& bounds
|
||||||
.intersect(&cx.content_mask().bounds)
|
.intersect(&cx.content_mask().bounds)
|
||||||
|
@ -1990,7 +2003,7 @@ impl Interactivity {
|
||||||
cx.stacking_order(),
|
cx.stacking_order(),
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
style.refine(drag_over_style);
|
style.refine(&build_drag_over_style(drag.value.as_ref(), cx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1368,7 +1368,7 @@ impl ProjectPanel {
|
||||||
entry_id: *entry_id,
|
entry_id: *entry_id,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.drag_over::<ProjectEntryId>(|style| {
|
.drag_over::<ProjectEntryId>(|style, _, cx| {
|
||||||
style.bg(cx.theme().colors().drop_target_background)
|
style.bg(cx.theme().colors().drop_target_background)
|
||||||
})
|
})
|
||||||
.on_drop(cx.listener(move |this, dragged_id: &ProjectEntryId, cx| {
|
.on_drop(cx.listener(move |this, dragged_id: &ProjectEntryId, cx| {
|
||||||
|
|
|
@ -181,7 +181,9 @@ message Envelope {
|
||||||
MarkNotificationRead mark_notification_read = 153;
|
MarkNotificationRead mark_notification_read = 153;
|
||||||
LspExtExpandMacro lsp_ext_expand_macro = 154;
|
LspExtExpandMacro lsp_ext_expand_macro = 154;
|
||||||
LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155;
|
LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155;
|
||||||
SetRoomParticipantRole set_room_participant_role = 156; // Current max
|
SetRoomParticipantRole set_room_participant_role = 156;
|
||||||
|
|
||||||
|
UpdateUserChannels update_user_channels = 157; // current max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +212,10 @@ enum ErrorCode {
|
||||||
Forbidden = 5;
|
Forbidden = 5;
|
||||||
WrongReleaseChannel = 6;
|
WrongReleaseChannel = 6;
|
||||||
NeedsCla = 7;
|
NeedsCla = 7;
|
||||||
|
NotARootChannel = 8;
|
||||||
|
BadPublicNesting = 9;
|
||||||
|
CircularNesting = 10;
|
||||||
|
WrongMoveTarget = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Test {
|
message Test {
|
||||||
|
@ -992,21 +998,26 @@ message UpdateChannels {
|
||||||
repeated Channel channel_invitations = 5;
|
repeated Channel channel_invitations = 5;
|
||||||
repeated uint64 remove_channel_invitations = 6;
|
repeated uint64 remove_channel_invitations = 6;
|
||||||
repeated ChannelParticipants channel_participants = 7;
|
repeated ChannelParticipants channel_participants = 7;
|
||||||
repeated UnseenChannelMessage unseen_channel_messages = 9;
|
repeated ChannelMessageId latest_channel_message_ids = 8;
|
||||||
repeated UnseenChannelBufferChange unseen_channel_buffer_changes = 10;
|
repeated ChannelBufferVersion latest_channel_buffer_versions = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UnseenChannelMessage {
|
message UpdateUserChannels {
|
||||||
|
repeated ChannelMessageId observed_channel_message_id = 1;
|
||||||
|
repeated ChannelBufferVersion observed_channel_buffer_version = 2;
|
||||||
|
repeated ChannelMembership channel_memberships = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ChannelMembership {
|
||||||
|
uint64 channel_id = 1;
|
||||||
|
ChannelRole role = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ChannelMessageId {
|
||||||
uint64 channel_id = 1;
|
uint64 channel_id = 1;
|
||||||
uint64 message_id = 2;
|
uint64 message_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UnseenChannelBufferChange {
|
|
||||||
uint64 channel_id = 1;
|
|
||||||
uint64 epoch = 2;
|
|
||||||
repeated VectorClockEntry version = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ChannelPermission {
|
message ChannelPermission {
|
||||||
uint64 channel_id = 1;
|
uint64 channel_id = 1;
|
||||||
ChannelRole role = 3;
|
ChannelRole role = 3;
|
||||||
|
@ -1041,7 +1052,6 @@ message ChannelMember {
|
||||||
enum Kind {
|
enum Kind {
|
||||||
Member = 0;
|
Member = 0;
|
||||||
Invitee = 1;
|
Invitee = 1;
|
||||||
AncestorMember = 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1148,7 +1158,7 @@ message GetChannelMessagesById {
|
||||||
|
|
||||||
message MoveChannel {
|
message MoveChannel {
|
||||||
uint64 channel_id = 1;
|
uint64 channel_id = 1;
|
||||||
optional uint64 to = 2;
|
uint64 to = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message JoinChannelBuffer {
|
message JoinChannelBuffer {
|
||||||
|
@ -1586,7 +1596,6 @@ message Channel {
|
||||||
uint64 id = 1;
|
uint64 id = 1;
|
||||||
string name = 2;
|
string name = 2;
|
||||||
ChannelVisibility visibility = 3;
|
ChannelVisibility visibility = 3;
|
||||||
ChannelRole role = 4;
|
|
||||||
repeated uint64 parent_path = 5;
|
repeated uint64 parent_path = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -269,6 +269,7 @@ messages!(
|
||||||
(UpdateChannelBuffer, Foreground),
|
(UpdateChannelBuffer, Foreground),
|
||||||
(UpdateChannelBufferCollaborators, Foreground),
|
(UpdateChannelBufferCollaborators, Foreground),
|
||||||
(UpdateChannels, Foreground),
|
(UpdateChannels, Foreground),
|
||||||
|
(UpdateUserChannels, Foreground),
|
||||||
(UpdateContacts, Foreground),
|
(UpdateContacts, Foreground),
|
||||||
(UpdateDiagnosticSummary, Foreground),
|
(UpdateDiagnosticSummary, Foreground),
|
||||||
(UpdateDiffBase, Foreground),
|
(UpdateDiffBase, Foreground),
|
||||||
|
|
|
@ -11,4 +11,4 @@ pub use notification::*;
|
||||||
pub use peer::*;
|
pub use peer::*;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub const PROTOCOL_VERSION: u32 = 67;
|
pub const PROTOCOL_VERSION: u32 = 68;
|
||||||
|
|
|
@ -1334,8 +1334,12 @@ impl Pane {
|
||||||
},
|
},
|
||||||
|tab, cx| cx.new_view(|_| tab.clone()),
|
|tab, cx| cx.new_view(|_| tab.clone()),
|
||||||
)
|
)
|
||||||
.drag_over::<DraggedTab>(|tab| tab.bg(cx.theme().colors().drop_target_background))
|
.drag_over::<DraggedTab>(|tab, _, cx| {
|
||||||
.drag_over::<ProjectEntryId>(|tab| tab.bg(cx.theme().colors().drop_target_background))
|
tab.bg(cx.theme().colors().drop_target_background)
|
||||||
|
})
|
||||||
|
.drag_over::<ProjectEntryId>(|tab, _, cx| {
|
||||||
|
tab.bg(cx.theme().colors().drop_target_background)
|
||||||
|
})
|
||||||
.when_some(self.can_drop_predicate.clone(), |this, p| {
|
.when_some(self.can_drop_predicate.clone(), |this, p| {
|
||||||
this.can_drop(move |a, cx| p(a, cx))
|
this.can_drop(move |a, cx| p(a, cx))
|
||||||
})
|
})
|
||||||
|
@ -1505,10 +1509,10 @@ impl Pane {
|
||||||
.child("")
|
.child("")
|
||||||
.h_full()
|
.h_full()
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.drag_over::<DraggedTab>(|bar| {
|
.drag_over::<DraggedTab>(|bar, _, cx| {
|
||||||
bar.bg(cx.theme().colors().drop_target_background)
|
bar.bg(cx.theme().colors().drop_target_background)
|
||||||
})
|
})
|
||||||
.drag_over::<ProjectEntryId>(|bar| {
|
.drag_over::<ProjectEntryId>(|bar, _, cx| {
|
||||||
bar.bg(cx.theme().colors().drop_target_background)
|
bar.bg(cx.theme().colors().drop_target_background)
|
||||||
})
|
})
|
||||||
.on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
|
.on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue