Fetch and wait for channels when opening channel notes via URL (#33291)

Release Notes:

* Collaboration: Now fetches and waits for channels when opening channel
notes via URL.
This commit is contained in:
Michael Sloan 2025-06-23 23:14:15 -06:00 committed by GitHub
parent 24c94d474e
commit 786e724684
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 99 additions and 49 deletions

View file

@ -24,6 +24,7 @@ futures.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true
postage.workspace = true
rand.workspace = true
release_channel.workspace = true
rpc.workspace = true

View file

@ -4,13 +4,14 @@ use crate::{ChannelMessage, channel_buffer::ChannelBuffer, channel_chat::Channel
use anyhow::{Context as _, Result, anyhow};
use channel_index::ChannelIndex;
use client::{ChannelId, Client, ClientSettings, Subscription, User, UserId, UserStore};
use collections::{HashMap, HashSet, hash_map};
use collections::{HashMap, HashSet};
use futures::{Future, FutureExt, StreamExt, channel::mpsc, future::Shared};
use gpui::{
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Global, SharedString, Task,
WeakEntity,
};
use language::Capability;
use postage::{sink::Sink, watch};
use rpc::{
TypedEnvelope,
proto::{self, ChannelRole, ChannelVisibility},
@ -43,6 +44,7 @@ pub struct ChannelStore {
opened_chats: HashMap<ChannelId, OpenEntityHandle<ChannelChat>>,
client: Arc<Client>,
did_subscribe: bool,
channels_loaded: (watch::Sender<bool>, watch::Receiver<bool>),
user_store: Entity<UserStore>,
_rpc_subscriptions: [Subscription; 2],
_watch_connection_status: Task<Option<()>>,
@ -219,6 +221,7 @@ impl ChannelStore {
}),
channel_states: Default::default(),
did_subscribe: false,
channels_loaded: watch::channel_with(false),
}
}
@ -234,6 +237,48 @@ impl ChannelStore {
}
}
pub fn wait_for_channels(
&mut self,
timeout: Duration,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let mut channels_loaded_rx = self.channels_loaded.1.clone();
if *channels_loaded_rx.borrow() {
return Task::ready(Ok(()));
}
let mut status_receiver = self.client.status();
if status_receiver.borrow().is_connected() {
self.initialize();
}
let mut timer = cx.background_executor().timer(timeout).fuse();
cx.spawn(async move |this, cx| {
loop {
futures::select_biased! {
channels_loaded = channels_loaded_rx.next().fuse() => {
if let Some(true) = channels_loaded {
return Ok(());
}
}
status = status_receiver.next().fuse() => {
if let Some(status) = status {
if status.is_connected() {
this.update(cx, |this, _cx| {
this.initialize();
}).ok();
}
}
continue;
}
_ = timer => {
return Err(anyhow!("{:?} elapsed without receiving channels", timeout));
}
}
}
})
}
pub fn client(&self) -> Arc<Client> {
self.client.clone()
}
@ -309,6 +354,7 @@ impl ChannelStore {
let channel_store = cx.entity();
self.open_channel_resource(
channel_id,
"notes",
|this| &mut this.opened_buffers,
async move |channel, cx| {
ChannelBuffer::new(channel, client, user_store, channel_store, cx).await
@ -439,6 +485,7 @@ impl ChannelStore {
let this = cx.entity();
self.open_channel_resource(
channel_id,
"chat",
|this| &mut this.opened_chats,
async move |channel, cx| ChannelChat::new(channel, this, user_store, client, cx).await,
cx,
@ -453,6 +500,7 @@ impl ChannelStore {
fn open_channel_resource<T, F>(
&mut self,
channel_id: ChannelId,
resource_name: &'static str,
get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenEntityHandle<T>>,
load: F,
cx: &mut Context<Self>,
@ -462,58 +510,56 @@ impl ChannelStore {
T: 'static,
{
let task = loop {
match get_map(self).entry(channel_id) {
hash_map::Entry::Occupied(e) => match e.get() {
OpenEntityHandle::Open(entity) => {
if let Some(entity) = entity.upgrade() {
break Task::ready(Ok(entity)).shared();
} else {
get_map(self).remove(&channel_id);
continue;
}
match get_map(self).get(&channel_id) {
Some(OpenEntityHandle::Open(entity)) => {
if let Some(entity) = entity.upgrade() {
break Task::ready(Ok(entity)).shared();
} else {
get_map(self).remove(&channel_id);
continue;
}
OpenEntityHandle::Loading(task) => {
break task.clone();
}
},
hash_map::Entry::Vacant(e) => {
let task = cx
.spawn(async move |this, cx| {
let channel = this.read_with(cx, |this, _| {
this.channel_for_id(channel_id).cloned().ok_or_else(|| {
Arc::new(anyhow!("no channel for id: {channel_id}"))
})
})??;
load(channel, cx).await.map_err(Arc::new)
})
.shared();
e.insert(OpenEntityHandle::Loading(task.clone()));
cx.spawn({
let task = task.clone();
async move |this, cx| {
let result = task.await;
this.update(cx, |this, _| match result {
Ok(model) => {
get_map(this).insert(
channel_id,
OpenEntityHandle::Open(model.downgrade()),
);
}
Err(_) => {
get_map(this).remove(&channel_id);
}
})
.ok();
}
})
.detach();
break task;
}
Some(OpenEntityHandle::Loading(task)) => break task.clone(),
None => {}
}
let channels_ready = self.wait_for_channels(Duration::from_secs(10), cx);
let task = cx
.spawn(async move |this, cx| {
channels_ready.await?;
let channel = this.read_with(cx, |this, _| {
this.channel_for_id(channel_id)
.cloned()
.ok_or_else(|| Arc::new(anyhow!("no channel for id: {channel_id}")))
})??;
load(channel, cx).await.map_err(Arc::new)
})
.shared();
get_map(self).insert(channel_id, OpenEntityHandle::Loading(task.clone()));
let task = cx.spawn({
async move |this, cx| {
let result = task.await;
this.update(cx, |this, _| match &result {
Ok(model) => {
get_map(this)
.insert(channel_id, OpenEntityHandle::Open(model.downgrade()));
}
Err(_) => {
get_map(this).remove(&channel_id);
}
})?;
result
}
});
break task.shared();
};
cx.background_spawn(async move { task.await.map_err(|error| anyhow!("{error}")) })
cx.background_spawn(async move {
task.await.map_err(|error| {
anyhow!("{error}").context(format!("failed to open channel {resource_name}"))
})
})
}
pub fn is_channel_admin(&self, channel_id: ChannelId) -> bool {
@ -1147,6 +1193,8 @@ impl ChannelStore {
.or_default()
.update_latest_message_id(latest_channel_message.message_id);
}
self.channels_loaded.0.try_send(true).log_err();
}
cx.notify();