daydream

A small matrix web client written in rust
git clone git://archive.git.mtrnord.blog/daydream-mx/daydream.git
Log | Files | Refs | README | LICENSE

commit ade988a190a6b63c67b1ae7cd59818220e8485b3
parent e86de62fa427004e847c9bbf21fb9b701925a23a
Author: Marcel <mtrnord1@gmail.com>
Date:   Thu, 28 May 2020 19:37:06 +0200

Optimize Sync flow

Took 2 hours 6 minutes

Diffstat:
MCargo.toml | 3+--
Msrc/app.rs | 2+-
Msrc/app/components/event_list.rs | 19+++++++------------
Msrc/app/components/room_list.rs | 37++++++++++++++++++++++++++++++++-----
Msrc/app/matrix.rs | 106+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/app/matrix/sync.rs | 83+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/app/matrix/types.rs | 56++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/app/views/main_view.rs | 28++++++++--------------------
8 files changed, 199 insertions(+), 135 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -23,11 +23,10 @@ wee_alloc = "0.4" yew = { git = "https://github.com/yewstack/yew" } yew-router = { git = "https://github.com/yewstack/yew" } yewtil = { git = "https://github.com/yewstack/yew" } -matrix-sdk = { version = "0.1.0", git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false} +matrix-sdk = { version = "0.1.0", git = "https://github.com/MTRNord/matrix-rust-sdk", branch = "expose-send-rebased", default-features = false} url = "2.1.1" thiserror = "1.0" futures-locks = { git = "https://github.com/asomers/futures-locks", default-features = false } -js_int = "0.1.5" linked_hash_set = {version = "0.1.3", features = ["serde"] } [dev-dependencies] diff --git a/src/app.rs b/src/app.rs @@ -1,10 +1,10 @@ use log::*; use yew::{prelude::*, virtual_dom::VNode}; +use yew_router::agent::RouteRequest::ChangeRoute; use yew_router::{prelude::*, Switch}; use crate::app::matrix::{MatrixAgent, Response}; use crate::app::views::{login::Login, main_view::MainView}; -use yew_router::agent::RouteRequest::ChangeRoute; pub mod components; mod matrix; diff --git a/src/app/components/event_list.rs b/src/app/components/event_list.rs @@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize}; use yew::prelude::*; use yewtil::NeqAssign; -use crate::app::matrix::{MatrixAgent, Request, Response}; use crate::app::matrix::types::MessageWrapper; +use crate::app::matrix::{MatrixAgent, Request, Response}; pub struct EventList { link: ComponentLink<Self>, @@ -50,7 +50,7 @@ impl Component for EventList { if state .events .iter() - .filter(|x| x.room_id == room_id) + .filter(|x| x.room_id.clone().unwrap() == room_id) .collect::<LinkedHashSet<&MessageWrapper>>() .is_empty() { @@ -58,8 +58,6 @@ impl Component for EventList { } } - - EventList { props, link, @@ -80,19 +78,17 @@ impl Component for EventList { Response::OldMessages(messages) => { // TODO this doesn't seem smart let mut new_events_map = LinkedHashSet::new(); - for event in messages.into_iter() { + for event in self.state.events.clone().into_iter() { new_events_map.insert(event); } - for event in self.state.events.clone().into_iter() { + for event in messages.into_iter() { new_events_map.insert(event); } self.state.events = new_events_map; true } - _ => { - false - } + _ => false, } } } @@ -107,7 +103,7 @@ impl Component for EventList { <div class="container uk-flex uk-flex-column uk-width-5-6 uk-padding uk-padding-remove-bottom" style="height: 100%"> <h1>{ self.props.displayname.clone() }</h1> <div class="scrollable" style="height: 100%"> - { self.state.events.iter().filter(|x| x.room_id == self.props.current_room.clone().unwrap()).map(|event| self.get_event(event.clone())).collect::<Html>() } + { self.state.events.iter().filter(|x| x.room_id.clone().unwrap() == self.props.current_room.clone().unwrap()).map(|event| self.get_event(event.clone())).collect::<Html>() } <div id="anchor"></div> </div> </div> @@ -115,11 +111,10 @@ impl Component for EventList { } } - impl EventList { fn get_event(&self, event: MessageWrapper) -> Html { html! { - <p>{event.sender_displayname.clone()}{": "}{event.content.clone()}</p> + <p>{event.sender_displayname.unwrap_or(event.sender.to_string()).clone()}{": "}{event.content.clone()}</p> } } } diff --git a/src/app/components/room_list.rs b/src/app/components/room_list.rs @@ -1,15 +1,14 @@ use std::collections::HashMap; -use js_int::UInt; use log::*; -use matrix_sdk::identifiers::RoomId; +use matrix_sdk::{identifiers::RoomId, js_int::UInt}; use serde::{Deserialize, Serialize}; use yew::prelude::*; use yew::{Bridge, Bridged, Component, ComponentLink, Html}; use yewtil::NeqAssign; use crate::app::matrix::types::SmallRoom; -use crate::app::matrix::{MatrixAgent, Response}; +use crate::app::matrix::{MatrixAgent, Request, Response}; pub struct RoomList { link: ComponentLink<Self>, @@ -49,7 +48,7 @@ impl Component for RoomList { rooms: Default::default(), current_room: None, loading: true, - search_query: None + search_query: None, }; RoomList { @@ -68,14 +67,41 @@ impl Component for RoomList { self.state.loading = false; true } + // Handle new rooms from sync + Response::Sync(msg) => { + info!("room_list sync_message: {:#?}", msg); + if !(self + .state + .rooms + .keys() + .map(|x| x.clone()) + .collect::<Vec<RoomId>>() + .contains(&msg.room_id.clone().unwrap())) + { + self.matrix_agent + .send(Request::GetJoinedRoom(msg.room_id.clone().unwrap())); + } + true + } + Response::JoinedRoom((room_id, room)) => { + self.state.rooms.insert(room_id, room); + true + } _ => false, }, Msg::ChangeRoom(room) => { - let displayname = self.state.rooms.iter().filter(|(id, _)| **id == room).map(|(_, room)| room.name.clone()).collect::<String>(); + let displayname = self + .state + .rooms + .iter() + .filter(|(id, _)| **id == room) + .map(|(_, room)| room.name.clone()) + .collect::<String>(); self.props.change_room_callback.emit((displayname, room)); false } Msg::SetFilter(query) => { + info!("{}", query); self.state.search_query = Some(query); true } @@ -105,6 +131,7 @@ impl Component for RoomList { class="uk-search-input" type="search" placeholder="Filter Rooms..." + value=&self.state.search_query.clone().unwrap_or("".to_string()) oninput=self.link.callback(|e: InputData| Msg::SetFilter(e.value)) /> </form> </div> diff --git a/src/app/matrix.rs b/src/app/matrix.rs @@ -1,16 +1,16 @@ use std::collections::{HashMap, HashSet}; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::sync::{Arc, Mutex}; use futures_locks::RwLock; -use js_int::UInt; use linked_hash_set::LinkedHashSet; use log::*; use matrix_sdk::{ api::r0::{filter::RoomEventFilter, message::get_message_events::Direction}, events::collections::all::RoomEvent, - events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, + events::room::message::MessageEventContent, identifiers::RoomId, + js_int::UInt, Client, ClientConfig, MessagesRequestBuilder, Room, Session, }; use serde::{Deserialize, Serialize}; @@ -64,6 +64,7 @@ pub enum Request { GetOldMessages((RoomId, Option<String>)), StartSync, GetJoinedRooms, + GetJoinedRoom(RoomId), } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -76,11 +77,17 @@ pub enum Response { JoinedRoomList(HashMap<RoomId, SmallRoom>), Userdata(), OldMessages(LinkedHashSet<MessageWrapper>), + JoinedRoom((RoomId, SmallRoom)), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum Msg { + OnSyncResponse(Response), } impl Agent for MatrixAgent { type Reach = Context<MatrixAgent>; - type Message = (); + type Message = Msg; type Input = Request; type Output = Response; @@ -105,7 +112,15 @@ impl Agent for MatrixAgent { } } - fn update(&mut self, _: Self::Message) {} + fn update(&mut self, msg: Self::Message) { + match msg { + Msg::OnSyncResponse(resp) => { + for sub in self.subscribers.iter() { + self.link.respond(*sub, resp.clone()); + } + } + } + } fn connected(&mut self, id: HandlerId) { self.subscribers.insert(id); @@ -267,42 +282,29 @@ impl Agent for MatrixAgent { let mut wrapped_messages: LinkedHashSet<MessageWrapper> = LinkedHashSet::new(); for event in messsages.chunk.iter().rev() { if let Ok(event) = event.deserialize() { - if let RoomEvent::RoomMessage(MessageEvent { - content: - MessageEventContent::Text(TextMessageEventContent { - body: msg_body, - .. - }), - sender, - .. - }) = event - { - let name = { - let room: Arc<RwLock<Room>> = client - .clone() - .get_joined_room(&room_id.clone()) - .await - .unwrap(); - let room = room.read().await; - match room.members.get(&sender) { - Some(member) => member - .display_name - .as_ref() - .map(ToString::to_string) - .unwrap_or(sender.to_string()), - None => sender.to_string(), + match event { + RoomEvent::RoomMessage(event) => match event.content { + MessageEventContent::Text(_) => { + let mut wrapped_event: MessageWrapper = + event.try_into().expect("m.room.message"); + + if wrapped_event.room_id.is_none() { + wrapped_event.room_id = Some(room_id.clone()); + } + + wrapped_event.sender_displayname = Some( + wrapped_event.get_displayname(client.clone()).await, + ); + wrapped_messages.insert(wrapped_event); } - }; - - let wrapper = MessageWrapper { - sender_displayname: name.clone(), - room_id: room_id.clone(), - content: msg_body.clone(), - }; - wrapped_messages.insert(wrapper); - } else { - continue; - }; + _ => { + return; + } + }, + _ => { + continue; + } + } } } @@ -312,6 +314,24 @@ impl Agent for MatrixAgent { } }); } + Request::GetJoinedRoom(room_id) => { + let agent = self.clone(); + let client = self.matrix_client.clone().unwrap(); + spawn_local(async move { + let room: Arc<RwLock<Room>> = + client.get_joined_room(&room_id.clone()).await.unwrap(); + let small_room = SmallRoom { + name: room.read().await.display_name(), + unread_notifications: room.read().await.unread_notifications.clone(), + unread_highlight: room.read().await.unread_highlight.clone(), + id: room_id.clone(), + }; + for sub in agent.subscribers.iter() { + let resp = Response::JoinedRoom((room_id.clone(), small_room.clone())); + agent.link.respond(*sub, resp); + } + }); + } } } @@ -328,11 +348,7 @@ impl MatrixAgent { async fn start_sync(&self) { let sync = sync::Sync { matrix_client: self.matrix_client.clone().unwrap(), - callback: |x: Response| { - for sub in self.subscribers.iter() { - self.link.respond(*sub, x.clone()); - } - }, + callback: self.link.callback(Msg::OnSyncResponse), }; sync.start_sync().await; } diff --git a/src/app/matrix/sync.rs b/src/app/matrix/sync.rs @@ -1,35 +1,28 @@ -use crate::app::matrix::types::MessageWrapper; -use crate::app::matrix::Response; -use futures_locks::RwLock; +use std::convert::TryInto; + use log::*; use matrix_sdk::{ - api::r0::sync::sync_events::Response as SyncResponse, - events::collections::all::RoomEvent, - events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, - identifiers::RoomId, - Client, Room, SyncSettings, + api::r0::sync::sync_events::Response as SyncResponse, events::collections::all::RoomEvent, + events::room::message::MessageEventContent, identifiers::RoomId, Client, SyncSettings, }; -use std::sync::Arc; -pub struct Sync<F> -where - F: Fn(Response) + std::marker::Sync, -{ +use crate::app::matrix::types::MessageWrapper; +use crate::app::matrix::Response; +use yew::Callback; + +pub struct Sync { pub(crate) matrix_client: Client, - pub(crate) callback: F, + pub(crate) callback: Callback<Response>, } -impl<F> Sync<F> -where - F: Fn(Response) + std::marker::Sync, -{ +impl Sync { pub async fn start_sync(&self) { let client = self.matrix_client.clone(); let resp = client.clone().sync(SyncSettings::default()).await; match resp { Ok(_) => { let resp = Response::FinishedFirstSync; - (self.callback)(resp); + self.callback.emit(resp); } _ => {} } @@ -53,34 +46,32 @@ where async fn on_room_message(&self, room_id: &RoomId, event: RoomEvent) { // TODO handle all messages... - if let RoomEvent::RoomMessage(MessageEvent { - content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), - sender, - .. - }) = event - { - let name = { - let room: Arc<RwLock<Room>> = - self.matrix_client.get_joined_room(room_id).await.unwrap(); - let room = room.read().await; - let member = room.members.get(&sender).unwrap(); - member - .display_name - .as_ref() - .map(ToString::to_string) - .unwrap_or(sender.to_string()) - }; - let wrapper = MessageWrapper { - sender_displayname: name.clone(), - room_id: room_id.clone(), - content: msg_body.clone(), - }; + match event { + RoomEvent::RoomMessage(event) => match event.content { + MessageEventContent::Text(_) => { + let mut wrapped_event: MessageWrapper = + event.try_into().expect("m.room.message"); - let resp = Response::Sync(wrapper); - (self.callback)(resp); - } else { - return; - }; + if wrapped_event.room_id.is_none() { + wrapped_event.room_id = Some(room_id.clone()); + } + + wrapped_event.sender_displayname = Some( + wrapped_event + .get_displayname(self.matrix_client.clone()) + .await, + ); + let resp = Response::Sync(wrapped_event); + self.callback.emit(resp); + } + _ => { + return; + } + }, + _ => { + return; + } + } } } diff --git a/src/app/matrix/types.rs b/src/app/matrix/types.rs @@ -1,7 +1,16 @@ -use js_int::UInt; -use matrix_sdk::identifiers::RoomId; +use std::convert::TryFrom; +use std::sync::Arc; + +use futures_locks::RwLock; +use matrix_sdk::{ + events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, + identifiers::{RoomId, UserId}, + js_int::UInt, + Client, Room, +}; use serde::{Deserialize, Serialize}; +// TODO: Add Into trait #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SmallRoom { pub(crate) name: String, @@ -12,8 +21,47 @@ pub struct SmallRoom { #[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] pub struct MessageWrapper { - pub(crate) sender_displayname: String, - pub(crate) room_id: RoomId, + pub(crate) sender_displayname: Option<String>, + pub(crate) room_id: Option<RoomId>, + pub(crate) sender: UserId, // TODO use ruma structs pub(crate) content: String, } + +impl MessageWrapper { + pub async fn get_displayname(&self, client: Client) -> String { + let room: Arc<RwLock<Room>> = client + .get_joined_room(&self.room_id.clone().unwrap()) + .await + .unwrap(); + let room = room.read().await; + let member = room.members.get(&self.sender.clone()).unwrap(); + member + .display_name + .as_ref() + .map(ToString::to_string) + .unwrap_or(self.sender.clone().to_string()) + } +} + +impl TryFrom<MessageEvent> for MessageWrapper { + type Error = (); + + fn try_from(event: MessageEvent) -> Result<Self, Self::Error> { + return if let MessageEvent { + content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), + sender, + .. + } = event + { + Ok(MessageWrapper { + sender_displayname: None, // We cant get it without a Client and therefor cant calculate it here + room_id: event.room_id, + sender, + content: msg_body, + }) + } else { + Err(()) + }; + } +} diff --git a/src/app/views/main_view.rs b/src/app/views/main_view.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use yew::prelude::*; use yew::ComponentLink; -use crate::app::components::{room_list::RoomList, event_list::EventList}; +use crate::app::components::{event_list::EventList, room_list::RoomList}; use crate::app::matrix::types::MessageWrapper; use crate::app::matrix::{MatrixAgent, Request, Response}; @@ -41,7 +41,7 @@ impl Component for MainView { let state = State { events: Default::default(), current_room: None, - current_room_displayname: Default::default() + current_room_displayname: Default::default(), }; MainView { @@ -53,27 +53,15 @@ impl Component for MainView { fn update(&mut self, msg: Self::Message) -> bool { match msg { - Msg::NewMessage(response) => { - match response { - Response::FinishedFirstSync => { - self.matrix_agent.send(Request::GetJoinedRooms); - } - _ => {} + Msg::NewMessage(response) => match response { + Response::FinishedFirstSync => { + self.matrix_agent.send(Request::GetJoinedRooms); } - } + _ => {} + }, Msg::ChangeRoom((displayname, room)) => { let room_id = RoomId::try_from(room).unwrap(); - if self - .state - .events - .iter() - .filter(|x| x.room_id == room_id) - .collect::<LinkedHashSet<&MessageWrapper>>() - .is_empty() - { - self.matrix_agent - .send(Request::GetOldMessages((room_id.clone(), None))); - } + self.state.current_room = Some(room_id.clone()); self.state.current_room_displayname = displayname; }