From f0019e3725d32f72d7fdbaae956cf1d787b8f19c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 14 Sep 2021 12:29:56 -0600 Subject: [PATCH] WIP --- gpui/src/app.rs | 10 +++++ server/src/rpc.rs | 10 ++--- zed/src/channel.rs | 4 +- zed/src/main.rs | 2 +- zed/src/rpc.rs | 12 +++--- zed/src/test.rs | 2 +- zed/src/user.rs | 88 +++++++++++++++++++++++++++++++++++++++++--- zed/src/workspace.rs | 38 ++++++++++++++----- 8 files changed, 135 insertions(+), 31 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index c1ce8fdba0..4b18af06b4 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -2282,6 +2282,16 @@ impl<'a, T: View> ViewContext<'a, T> { let handle = self.handle(); self.app.spawn(|cx| f(handle, cx)) } + + pub fn spawn_weak(&self, f: F) -> Task + where + F: FnOnce(WeakViewHandle, AsyncAppContext) -> Fut, + Fut: 'static + Future, + S: 'static, + { + let handle = self.handle().downgrade(); + self.app.spawn(|cx| f(handle, cx)) + } } pub struct RenderContext<'a, T: View> { diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 2bd0eac625..539068f2d3 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -1512,7 +1512,7 @@ mod tests { .await .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)); channels_a .condition(&mut cx_a, |list, _| list.available_channels().is_some()) @@ -1537,7 +1537,7 @@ mod tests { }) .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)); channels_b .condition(&mut cx_b, |list, _| list.available_channels().is_some()) @@ -1637,7 +1637,7 @@ mod tests { .await .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)); channels_a .condition(&mut cx_a, |list, _| list.available_channels().is_some()) @@ -1713,7 +1713,7 @@ mod tests { .await .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)); channels_a .condition(&mut cx_a, |list, _| list.available_channels().is_some()) @@ -1739,7 +1739,7 @@ mod tests { }) .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)); channels_b .condition(&mut cx_b, |list, _| list.available_channels().is_some()) diff --git a/zed/src/channel.rs b/zed/src/channel.rs index 8e8b2964ff..bf1237d835 100644 --- a/zed/src/channel.rs +++ b/zed/src/channel.rs @@ -118,7 +118,7 @@ impl ChannelList { cx.notify(); }); } - rpc::Status::Disconnected { .. } => { + rpc::Status::SignedOut { .. } => { this.update(&mut cx, |this, cx| { this.available_channels = None; this.channels.clear(); @@ -503,7 +503,7 @@ mod tests { let user_id = 5; let mut client = Client::new(); 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)); channel_list.read_with(&cx, |list, _| assert_eq!(list.available_channels(), None)); diff --git a/zed/src/main.rs b/zed/src/main.rs index 852bfaec23..f585ed5b82 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -37,7 +37,7 @@ fn main() { app.run(move |cx| { 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 { languages: languages.clone(), settings_tx: Arc::new(Mutex::new(settings_tx)), diff --git a/zed/src/rpc.rs b/zed/src/rpc.rs index 501779c2b0..69bc33a62e 100644 --- a/zed/src/rpc.rs +++ b/zed/src/rpc.rs @@ -39,7 +39,7 @@ pub struct Client { #[derive(Copy, Clone, Debug)] pub enum Status { - Disconnected, + SignedOut, Authenticating, Connecting { user_id: u64, @@ -73,7 +73,7 @@ struct ClientState { impl Default for ClientState { fn default() -> Self { Self { - status: watch::channel_with(Status::Disconnected), + status: watch::channel_with(Status::SignedOut), entity_id_extractors: Default::default(), model_handlers: Default::default(), _maintain_connection: None, @@ -167,7 +167,7 @@ impl Client { } })); } - Status::Disconnected => { + Status::SignedOut => { state._maintain_connection.take(); } _ => {} @@ -232,7 +232,7 @@ impl Client { cx: &AsyncAppContext, ) -> anyhow::Result<()> { let was_disconnected = match *self.status().borrow() { - Status::Disconnected => true, + Status::SignedOut => true, Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { false } @@ -324,7 +324,7 @@ impl Client { cx.foreground() .spawn(async move { match handle_io.await { - Ok(()) => this.set_status(Status::Disconnected, &cx), + Ok(()) => this.set_status(Status::SignedOut, &cx), Err(err) => { log::error!("connection error: {:?}", err); this.set_status(Status::ConnectionLost, &cx); @@ -470,7 +470,7 @@ impl Client { pub async fn disconnect(self: &Arc, cx: &AsyncAppContext) -> Result<()> { let conn_id = self.connection_id()?; self.peer.disconnect(conn_id).await; - self.set_status(Status::Disconnected, cx); + self.set_status(Status::SignedOut, cx); Ok(()) } diff --git a/zed/src/test.rs b/zed/src/test.rs index 3557c0e3c4..019969ee4f 100644 --- a/zed/src/test.rs +++ b/zed/src/test.rs @@ -164,7 +164,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { let languages = Arc::new(LanguageRegistry::new()); let themes = ThemeRegistry::new(Assets, cx.font_cache().clone()); 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 { settings_tx: Arc::new(Mutex::new(settings_tx)), settings, diff --git a/zed/src/user.rs b/zed/src/user.rs index 9f821f048c..6c02c0a284 100644 --- a/zed/src/user.rs +++ b/zed/src/user.rs @@ -1,22 +1,73 @@ -use crate::rpc::Client; +use crate::{ + rpc::{Client, Status}, + util::TryFutureExt, +}; use anyhow::{anyhow, Result}; +use gpui::{elements::Image, executor, ImageData, Task}; use parking_lot::Mutex; +use postage::{prelude::Stream, sink::Sink, watch}; use std::{collections::HashMap, sync::Arc}; +use surf::{ + http::{Method, Request}, + HttpClient, Url, +}; use zrpc::proto; -pub use proto::User; +pub struct User { + id: u64, + github_login: String, + avatar: Option, +} pub struct UserStore { users: Mutex>>, + current_user: watch::Receiver>>, rpc: Arc, + http: Arc, + _maintain_current_user: Option>, } impl UserStore { - pub fn new(rpc: Arc) -> Self { - Self { + pub fn new( + rpc: Arc, + http: Arc, + executor: &executor::Background, + ) -> Arc { + let (mut current_user_tx, current_user_rx) = watch::channel(); + + let mut this = Arc::new(Self { 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) -> Result<()> { @@ -56,4 +107,29 @@ impl UserStore { Err(anyhow!("server responded with no users")) } } + + pub fn current_user(&self) -> &watch::Receiver>> { + &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> { + 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)) } diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index c603ec5bd4..1410b6ece5 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -29,7 +29,7 @@ use gpui::{ use log::error; pub use pane::*; pub use pane_group::*; -use postage::watch; +use postage::{prelude::Stream, watch}; use sidebar::{Side, Sidebar, ToggleSidebarItem}; use smol::prelude::*; use std::{ @@ -356,6 +356,7 @@ pub struct Workspace { (usize, Arc), postage::watch::Receiver, Arc>>>, >, + _observe_current_user: Task<()>, } impl Workspace { @@ -389,6 +390,18 @@ impl Workspace { ); 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 { modal: None, center: PaneGroup::new(pane.id()), @@ -404,6 +417,7 @@ impl Workspace { worktrees: Default::default(), items: Default::default(), loading_items: Default::default(), + _observe_current_user, } } @@ -940,17 +954,21 @@ impl Workspace { &self.active_pane } - fn render_account_status(&self, cx: &mut RenderContext) -> ElementBox { + fn render_current_user(&self, cx: &mut RenderContext) -> ElementBox { 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( Align::new( - ConstrainedBox::new( - Svg::new("icons/signed-out-12.svg") - .with_color(theme.workspace.titlebar.icon_signed_out) - .boxed(), - ) - .with_width(theme.workspace.titlebar.icon_width) - .boxed(), + ConstrainedBox::new(avatar) + .with_width(theme.workspace.titlebar.icon_width) + .boxed(), ) .boxed(), ) @@ -988,7 +1006,7 @@ impl View for Workspace { .boxed(), ) .with_child( - Align::new(self.render_account_status(cx)).right().boxed(), + Align::new(self.render_current_user(cx)).right().boxed(), ) .boxed(), )