This commit is contained in:
Nathan Sobo 2021-09-14 12:29:56 -06:00
parent 428c491542
commit f0019e3725
8 changed files with 135 additions and 31 deletions

View file

@ -2282,6 +2282,16 @@ impl<'a, T: View> ViewContext<'a, T> {
let handle = self.handle(); let handle = self.handle();
self.app.spawn(|cx| f(handle, cx)) self.app.spawn(|cx| f(handle, cx))
} }
pub fn spawn_weak<F, Fut, S>(&self, f: F) -> Task<S>
where
F: FnOnce(WeakViewHandle<T>, AsyncAppContext) -> Fut,
Fut: 'static + Future<Output = S>,
S: 'static,
{
let handle = self.handle().downgrade();
self.app.spawn(|cx| f(handle, cx))
}
} }
pub struct RenderContext<'a, T: View> { pub struct RenderContext<'a, T: View> {

View file

@ -1512,7 +1512,7 @@ mod tests {
.await .await
.unwrap(); .unwrap();
let user_store_a = Arc::new(UserStore::new(client_a.clone())); let user_store_a = UserStore::new(client_a.clone(), cx_a.background().as_ref());
let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx)); let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx));
channels_a channels_a
.condition(&mut cx_a, |list, _| list.available_channels().is_some()) .condition(&mut cx_a, |list, _| list.available_channels().is_some())
@ -1537,7 +1537,7 @@ mod tests {
}) })
.await; .await;
let user_store_b = Arc::new(UserStore::new(client_b.clone())); let user_store_b = UserStore::new(client_b.clone(), cx_b.background().as_ref());
let channels_b = cx_b.add_model(|cx| ChannelList::new(user_store_b, client_b, cx)); let channels_b = cx_b.add_model(|cx| ChannelList::new(user_store_b, client_b, cx));
channels_b channels_b
.condition(&mut cx_b, |list, _| list.available_channels().is_some()) .condition(&mut cx_b, |list, _| list.available_channels().is_some())
@ -1637,7 +1637,7 @@ mod tests {
.await .await
.unwrap(); .unwrap();
let user_store_a = Arc::new(UserStore::new(client_a.clone())); let user_store_a = UserStore::new(client_a.clone(), cx_a.background().as_ref());
let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx)); let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx));
channels_a channels_a
.condition(&mut cx_a, |list, _| list.available_channels().is_some()) .condition(&mut cx_a, |list, _| list.available_channels().is_some())
@ -1713,7 +1713,7 @@ mod tests {
.await .await
.unwrap(); .unwrap();
let user_store_a = Arc::new(UserStore::new(client_a.clone())); let user_store_a = UserStore::new(client_a.clone(), cx_a.background().as_ref());
let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx)); let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx));
channels_a channels_a
.condition(&mut cx_a, |list, _| list.available_channels().is_some()) .condition(&mut cx_a, |list, _| list.available_channels().is_some())
@ -1739,7 +1739,7 @@ mod tests {
}) })
.await; .await;
let user_store_b = Arc::new(UserStore::new(client_b.clone())); let user_store_b = UserStore::new(client_b.clone(), cx_b.background().as_ref());
let channels_b = cx_b.add_model(|cx| ChannelList::new(user_store_b, client_b, cx)); let channels_b = cx_b.add_model(|cx| ChannelList::new(user_store_b, client_b, cx));
channels_b channels_b
.condition(&mut cx_b, |list, _| list.available_channels().is_some()) .condition(&mut cx_b, |list, _| list.available_channels().is_some())

View file

@ -118,7 +118,7 @@ impl ChannelList {
cx.notify(); cx.notify();
}); });
} }
rpc::Status::Disconnected { .. } => { rpc::Status::SignedOut { .. } => {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.available_channels = None; this.available_channels = None;
this.channels.clear(); this.channels.clear();
@ -503,7 +503,7 @@ mod tests {
let user_id = 5; let user_id = 5;
let mut client = Client::new(); let mut client = Client::new();
let server = FakeServer::for_client(user_id, &mut client, &cx).await; let server = FakeServer::for_client(user_id, &mut client, &cx).await;
let user_store = Arc::new(UserStore::new(client.clone())); let user_store = UserStore::new(client.clone(), cx.background().as_ref());
let channel_list = cx.add_model(|cx| ChannelList::new(user_store, client.clone(), cx)); let channel_list = cx.add_model(|cx| ChannelList::new(user_store, client.clone(), cx));
channel_list.read_with(&cx, |list, _| assert_eq!(list.available_channels(), None)); channel_list.read_with(&cx, |list, _| assert_eq!(list.available_channels(), None));

View file

@ -37,7 +37,7 @@ fn main() {
app.run(move |cx| { app.run(move |cx| {
let rpc = rpc::Client::new(); let rpc = rpc::Client::new();
let user_store = Arc::new(UserStore::new(rpc.clone())); let user_store = UserStore::new(rpc.clone(), cx.background());
let app_state = Arc::new(AppState { let app_state = Arc::new(AppState {
languages: languages.clone(), languages: languages.clone(),
settings_tx: Arc::new(Mutex::new(settings_tx)), settings_tx: Arc::new(Mutex::new(settings_tx)),

View file

@ -39,7 +39,7 @@ pub struct Client {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum Status { pub enum Status {
Disconnected, SignedOut,
Authenticating, Authenticating,
Connecting { Connecting {
user_id: u64, user_id: u64,
@ -73,7 +73,7 @@ struct ClientState {
impl Default for ClientState { impl Default for ClientState {
fn default() -> Self { fn default() -> Self {
Self { Self {
status: watch::channel_with(Status::Disconnected), status: watch::channel_with(Status::SignedOut),
entity_id_extractors: Default::default(), entity_id_extractors: Default::default(),
model_handlers: Default::default(), model_handlers: Default::default(),
_maintain_connection: None, _maintain_connection: None,
@ -167,7 +167,7 @@ impl Client {
} }
})); }));
} }
Status::Disconnected => { Status::SignedOut => {
state._maintain_connection.take(); state._maintain_connection.take();
} }
_ => {} _ => {}
@ -232,7 +232,7 @@ impl Client {
cx: &AsyncAppContext, cx: &AsyncAppContext,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let was_disconnected = match *self.status().borrow() { let was_disconnected = match *self.status().borrow() {
Status::Disconnected => true, Status::SignedOut => true,
Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
false false
} }
@ -324,7 +324,7 @@ impl Client {
cx.foreground() cx.foreground()
.spawn(async move { .spawn(async move {
match handle_io.await { match handle_io.await {
Ok(()) => this.set_status(Status::Disconnected, &cx), Ok(()) => this.set_status(Status::SignedOut, &cx),
Err(err) => { Err(err) => {
log::error!("connection error: {:?}", err); log::error!("connection error: {:?}", err);
this.set_status(Status::ConnectionLost, &cx); this.set_status(Status::ConnectionLost, &cx);
@ -470,7 +470,7 @@ impl Client {
pub async fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) -> Result<()> { pub async fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) -> Result<()> {
let conn_id = self.connection_id()?; let conn_id = self.connection_id()?;
self.peer.disconnect(conn_id).await; self.peer.disconnect(conn_id).await;
self.set_status(Status::Disconnected, cx); self.set_status(Status::SignedOut, cx);
Ok(()) Ok(())
} }

View file

@ -164,7 +164,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
let languages = Arc::new(LanguageRegistry::new()); let languages = Arc::new(LanguageRegistry::new());
let themes = ThemeRegistry::new(Assets, cx.font_cache().clone()); let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
let rpc = rpc::Client::new(); let rpc = rpc::Client::new();
let user_store = Arc::new(UserStore::new(rpc.clone())); let user_store = UserStore::new(rpc.clone(), cx.background());
Arc::new(AppState { Arc::new(AppState {
settings_tx: Arc::new(Mutex::new(settings_tx)), settings_tx: Arc::new(Mutex::new(settings_tx)),
settings, settings,

View file

@ -1,22 +1,73 @@
use crate::rpc::Client; use crate::{
rpc::{Client, Status},
util::TryFutureExt,
};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use gpui::{elements::Image, executor, ImageData, Task};
use parking_lot::Mutex; use parking_lot::Mutex;
use postage::{prelude::Stream, sink::Sink, watch};
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use surf::{
http::{Method, Request},
HttpClient, Url,
};
use zrpc::proto; use zrpc::proto;
pub use proto::User; pub struct User {
id: u64,
github_login: String,
avatar: Option<ImageData>,
}
pub struct UserStore { pub struct UserStore {
users: Mutex<HashMap<u64, Arc<User>>>, users: Mutex<HashMap<u64, Arc<User>>>,
current_user: watch::Receiver<Option<Arc<User>>>,
rpc: Arc<Client>, rpc: Arc<Client>,
http: Arc<dyn HttpClient>,
_maintain_current_user: Option<Task<()>>,
} }
impl UserStore { impl UserStore {
pub fn new(rpc: Arc<Client>) -> Self { pub fn new(
Self { rpc: Arc<Client>,
http: Arc<dyn HttpClient>,
executor: &executor::Background,
) -> Arc<Self> {
let (mut current_user_tx, current_user_rx) = watch::channel();
let mut this = Arc::new(Self {
users: Default::default(), users: Default::default(),
rpc, current_user: current_user_rx,
} rpc: rpc.clone(),
http,
_maintain_current_user: None,
});
let task = {
let this = Arc::downgrade(&this);
executor.spawn(async move {
let mut status = rpc.status();
while let Some(status) = status.recv().await {
match status {
Status::Connected { user_id, .. } => {
if let Some(this) = this.upgrade() {
current_user_tx
.send(this.fetch_user(user_id).log_err().await)
.await
.ok();
}
}
Status::SignedOut => {
current_user_tx.send(None).await.ok();
}
_ => {}
}
}
})
};
Arc::get_mut(&mut this).unwrap()._maintain_current_user = Some(task);
this
} }
pub async fn load_users(&self, mut user_ids: Vec<u64>) -> Result<()> { pub async fn load_users(&self, mut user_ids: Vec<u64>) -> Result<()> {
@ -56,4 +107,29 @@ impl UserStore {
Err(anyhow!("server responded with no users")) Err(anyhow!("server responded with no users"))
} }
} }
pub fn current_user(&self) -> &watch::Receiver<Option<Arc<User>>> {
&self.current_user
}
}
impl User {
async fn new(message: proto::User, http: &dyn HttpClient) -> Self {
let avatar = fetch_avatar(http, &message.avatar_url).await.log_err();
User {
id: message.id,
github_login: message.github_login,
avatar,
}
}
}
async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result<Arc<ImageData>> {
let url = Url::parse(url)?;
let request = Request::new(Method::Get, url);
let response = http.send(request).await?;
let bytes = response.body_bytes().await?;
let format = image::guess_format(&bytes)?;
let image = image::load_from_memory_with_format(&bytes, format)?.into_bgra8();
Ok(ImageData::new(image))
} }

View file

@ -29,7 +29,7 @@ use gpui::{
use log::error; use log::error;
pub use pane::*; pub use pane::*;
pub use pane_group::*; pub use pane_group::*;
use postage::watch; use postage::{prelude::Stream, watch};
use sidebar::{Side, Sidebar, ToggleSidebarItem}; use sidebar::{Side, Sidebar, ToggleSidebarItem};
use smol::prelude::*; use smol::prelude::*;
use std::{ use std::{
@ -356,6 +356,7 @@ pub struct Workspace {
(usize, Arc<Path>), (usize, Arc<Path>),
postage::watch::Receiver<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>, postage::watch::Receiver<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
>, >,
_observe_current_user: Task<()>,
} }
impl Workspace { impl Workspace {
@ -389,6 +390,18 @@ impl Workspace {
); );
right_sidebar.add_item("icons/user-16.svg", cx.add_view(|_| ProjectBrowser).into()); right_sidebar.add_item("icons/user-16.svg", cx.add_view(|_| ProjectBrowser).into());
let mut current_user = app_state.user_store.current_user().clone();
let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
current_user.recv().await;
while current_user.recv().await.is_some() {
cx.update(|cx| {
if let Some(this) = this.upgrade(&cx) {
this.update(cx, |_, cx| cx.notify());
}
})
}
});
Workspace { Workspace {
modal: None, modal: None,
center: PaneGroup::new(pane.id()), center: PaneGroup::new(pane.id()),
@ -404,6 +417,7 @@ impl Workspace {
worktrees: Default::default(), worktrees: Default::default(),
items: Default::default(), items: Default::default(),
loading_items: Default::default(), loading_items: Default::default(),
_observe_current_user,
} }
} }
@ -940,17 +954,21 @@ impl Workspace {
&self.active_pane &self.active_pane
} }
fn render_account_status(&self, cx: &mut RenderContext<Self>) -> ElementBox { fn render_current_user(&self, cx: &mut RenderContext<Self>) -> ElementBox {
let theme = &self.settings.borrow().theme; let theme = &self.settings.borrow().theme;
let avatar = if let Some(current_user) = self.user_store.current_user().borrow().as_ref() {
todo!()
} else {
Svg::new("icons/signed-out-12.svg")
.with_color(theme.workspace.titlebar.icon_signed_out)
.boxed()
};
ConstrainedBox::new( ConstrainedBox::new(
Align::new( Align::new(
ConstrainedBox::new( ConstrainedBox::new(avatar)
Svg::new("icons/signed-out-12.svg") .with_width(theme.workspace.titlebar.icon_width)
.with_color(theme.workspace.titlebar.icon_signed_out) .boxed(),
.boxed(),
)
.with_width(theme.workspace.titlebar.icon_width)
.boxed(),
) )
.boxed(), .boxed(),
) )
@ -988,7 +1006,7 @@ impl View for Workspace {
.boxed(), .boxed(),
) )
.with_child( .with_child(
Align::new(self.render_account_status(cx)).right().boxed(), Align::new(self.render_current_user(cx)).right().boxed(),
) )
.boxed(), .boxed(),
) )