From ae8dba86f6c8ae5ef28f2d780f38054387bd5f0d Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 10 Dec 2023 22:01:05 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20Rearchitecting=20the=20?= =?UTF-8?q?interface=20with=20the=20MatrixClient?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace RwStore with channels. - Use of fermi to handle application data. - Use of tracing. --- Cargo.toml | 27 +- src/base.rs | 37 +- src/components/chats_window/chats_window.rs | 3 + src/components/contacts_window/contacts.rs | 56 +- .../contacts_window/contacts_section.rs | 64 +- .../contacts_window/contacts_window.rs | 12 +- src/components/contacts_window/user_infos.rs | 35 +- src/components/login.rs | 32 +- src/components/main_window.rs | 17 +- src/main.rs | 52 +- src/matrix_client.rs | 587 ++++++++---------- 11 files changed, 472 insertions(+), 450 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 41d2a62..64fa11c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,20 +6,27 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus = "0.4.0" -dioxus-desktop = "0.4.0" +dioxus = "0.4.3" +dioxus-desktop = "0.4.3" +dioxus-free-icons = { version = "0.7.0", features = ["material-design-icons-navigation", "ionicons"] } +dioxus-std = { version = "0.4.1", features = ["utils"] } +fermi = { version = "0.4.3" } + # matrix-sdk = { version = "0.6.2", features = ["js"] } matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", branch = "main" , features = ["js"]} -anyhow = "1.0.72" -url = "2.4.0" -tokio = "1.29.1" + +anyhow = "1.0.75" +url = "2.5.0" dirs = "5.0.1" ctrlc-async = "3.2.2" -tracing-subscriber = "0.3.17" -dioxus-free-icons = { version = "0.7.0", features = ["material-design-icons-navigation", "ionicons"] } -thiserror = "1.0.44" -turf = "0.5.0" -dioxus-std = { version = "0.4.0", features = ["utils"] } +tracing-subscriber = "0.3.18" +thiserror = "1.0.50" +turf = "0.7.0" +tokio = "1.34.0" +flume = "0.11.0" +log = "0.4.20" +tracing = "0.1.40" [build] target = "x86_64-unknown-linux-gnu" + diff --git a/src/base.rs b/src/base.rs index 9b1da7b..f447f2a 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,9 +1,6 @@ -use std::{ - collections::HashMap, - sync::{Arc, RwLock}, -}; +use std::{cell::RefCell, collections::HashMap, sync::Arc}; -use dioxus_std::utils::rw::UseRw; +use fermi::*; use matrix_sdk::room::Room as MatrixRoom; use matrix_sdk::{ room::RoomMember, @@ -36,7 +33,7 @@ impl UserInfo { #[derive(Clone, Debug)] pub struct Room { pub matrix_room: Arc, - pub topic: Option, + pub topic: Option>, pub members: HashMap, pub is_direct: Option, } @@ -44,7 +41,7 @@ pub struct Room { impl Room { pub fn new( matrix_room: Arc, - topic: Option, + topic: Option>, is_direct: Option, ) -> Self { Self { @@ -58,21 +55,23 @@ impl Room { pub fn name(&self) -> Option { self.matrix_room.name() } + + pub fn id(&self) -> OwnedRoomId { + OwnedRoomId::from(self.matrix_room.room_id()) + } } impl PartialEq for Room { fn eq(&self, other: &Self) -> bool { // TODO: Look for a better way to compare Matrix rooms self.matrix_room.room_id() == other.matrix_room.room_id() - && self.topic == other.topic - && self.is_direct == other.is_direct } } -pub type ByIdRooms = HashMap>>; +pub type ByIdRooms = HashMap>; pub type ByIdUserInfos = HashMap; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Store { pub is_logged: bool, pub rooms: ByIdRooms, @@ -80,7 +79,7 @@ pub struct Store { pub user_id: Option, } -impl<'a> Store { +impl Store { pub fn new() -> Self { Self { is_logged: false, @@ -107,15 +106,19 @@ impl PartialEq for Store { impl Eq for Store {} -pub type ReactiveStore = Arc>; - -#[derive(Clone)] pub struct AppSettings { - pub requester: Option>, + pub requester: Option, + pub store: Store, } impl AppSettings { pub fn new() -> Self { - Self { requester: None } + Self { + requester: None, + store: Store::new(), + } } } + +pub static APP_SETTINGS: AtomRef = AtomRef(|_| AppSettings::new()); +pub static ROOMS: AtomRef = AtomRef(|_| ByIdRooms::new()); diff --git a/src/components/chats_window/chats_window.rs b/src/components/chats_window/chats_window.rs index 2d5edff..2c5852a 100644 --- a/src/components/chats_window/chats_window.rs +++ b/src/components/chats_window/chats_window.rs @@ -1,4 +1,5 @@ use dioxus::prelude::*; +use tracing::debug; use crate::components::avatar_selector::AvatarSelector; use crate::components::chats_window::edit_section::EditSection; @@ -7,6 +8,8 @@ use crate::components::icons::DownArrowIcon; turf::style_sheet!("src/components/chats_window/chats_window.scss"); pub fn ChatsWindow(cx: Scope) -> Element { + debug!("ChatsWindow rendering"); + let room_names = vec![ "MON POTE", "Second room", diff --git a/src/components/contacts_window/contacts.rs b/src/components/contacts_window/contacts.rs index 0d16bb6..020982b 100644 --- a/src/components/contacts_window/contacts.rs +++ b/src/components/contacts_window/contacts.rs @@ -1,37 +1,47 @@ use std::cell::RefCell; +use std::time::Duration; use dioxus::prelude::*; -use dioxus_std::utils::rw::UseRw; +use fermi::*; +use tokio::time::sleep; +use tracing::debug; -use crate::base::Room; -use crate::base::Store; -use crate::components::contacts_window::contacts_section::ContactsSection; +use crate::base::AppSettings; +use crate::base::{ByIdRooms, Room, APP_SETTINGS, ROOMS}; +use crate::components::contacts_window::contacts_section::{ + filter_people_conversations, filter_room_conversations, ContactsSection, +}; turf::style_sheet!("src/components/contacts_window/contacts.scss"); -#[inline_props] -pub fn Contacts<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { - println!("Contacts rendering"); +pub fn Contacts(cx: Scope) -> Element { + debug!("Contacts rendering"); - let store = rw_store.read().unwrap(); + let app_settings_atom = use_atom_ref(cx, &APP_SETTINGS); + let rooms_atom = use_atom_ref(cx, &ROOMS); - let rooms = &store.rooms; + async fn sync_rooms(app_settings: UseAtomRef, rooms_atom: UseAtomRef) { + loop { + let requester = &app_settings.read().requester; - let rooms_len = rooms.len(); - let mut groups = Vec::::with_capacity(rooms_len); - let mut directs = Vec::::with_capacity(rooms_len); - - for arc_room in rooms.values() { - let room = arc_room.read().unwrap().to_owned(); - - let is_direct = room.is_direct.unwrap(); - if is_direct { - directs.push(room); - } else { - groups.push(room); + if requester.is_some() { + let rooms_receiver = &requester.as_ref().unwrap().rooms_receiver; + let room = rooms_receiver.recv_async().await.unwrap(); + let room_id = room.id(); + dbg!(&room_id); + rooms_atom + .write() + .insert(room_id, RefCell::::new(room)); + } else { + sleep(Duration::from_millis(1000)).await; + } } } + use_coroutine(cx, |_: UnboundedReceiver<()>| { + sync_rooms(app_settings_atom.clone(), rooms_atom.clone()) + }); + // TODO: Test overflow // TODO: Add offline users ? cx.render(rsx! { @@ -40,8 +50,8 @@ pub fn Contacts<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { div { class: ClassName::CONTACTS, - ContactsSection {name: "Groups", contacts: RefCell::new(groups)}, - ContactsSection {name: "Available", contacts: RefCell::new(directs)}, + ContactsSection {name: "Groups", filter: &filter_room_conversations}, + ContactsSection {name: "Available", filter: &filter_people_conversations}, }, }) } diff --git a/src/components/contacts_window/contacts_section.rs b/src/components/contacts_window/contacts_section.rs index a89b2ed..d821437 100644 --- a/src/components/contacts_window/contacts_section.rs +++ b/src/components/contacts_window/contacts_section.rs @@ -3,9 +3,11 @@ use std::cell::RefCell; use dioxus::prelude::*; use dioxus_free_icons::icons::io_icons::IoChevronDown; use dioxus_free_icons::Icon; +use fermi::prelude::*; use matrix_sdk::RoomState; +use tracing::{debug, warn}; -use crate::base::Room; +use crate::base::{ByIdRooms, Room, ROOMS}; turf::style_sheet!("src/components/contacts_window/contacts_section.scss"); @@ -19,11 +21,46 @@ fn ContactsArrow(cx: Scope) -> Element { }) } +static NO_NAME_REPR: &str = "No name"; static NO_SUBJECT_REPR: &str = "No subject"; -#[inline_props] -pub fn ContactsSection<'a>(cx: Scope, name: &'a str, contacts: RefCell>) -> Element { - println!("ContactsSection rendering"); +pub fn filter_people_conversations(rooms_atom: UseAtomRef) -> Vec> { + let rooms = rooms_atom.read(); + let mut filtered_rooms = Vec::>::with_capacity(rooms.len()); + + for room in rooms.values() { + let is_direct = room.borrow().is_direct.unwrap(); + if !is_direct { + filtered_rooms.push(room.to_owned()); + } + } + filtered_rooms +} + +pub fn filter_room_conversations(rooms_atom: UseAtomRef) -> Vec> { + let rooms = rooms_atom.read(); + let mut filtered_rooms = Vec::>::with_capacity(rooms.len()); + + for room in rooms.values() { + let is_direct = room.borrow().is_direct.unwrap(); + if is_direct { + filtered_rooms.push(room.to_owned()); + } + } + filtered_rooms +} + +#[component] +pub fn ContactsSection<'a>( + cx: Scope, + name: &'a str, + filter: &'a dyn Fn(UseAtomRef) -> Vec>, +) -> Element { + debug!("ContactsSection rendering"); + + let rooms_atom = use_atom_ref(cx, &ROOMS); + let contacts = filter(rooms_atom.clone()); + let contacts_len = contacts.len(); let show = use_state(cx, || false); @@ -33,11 +70,19 @@ pub fn ContactsSection<'a>(cx: Scope, name: &'a str, contacts: RefCell ] .join(" "); - let contacts_len = contacts.borrow().len(); + let rendered_contacts = contacts.into_iter().map(|room_ref| { + let room = room_ref.borrow(); + + let room_topic = room + .topic + .as_ref() + .unwrap_or(&RefCell::new(NO_SUBJECT_REPR.to_string())) + .borrow() + .to_owned(); + let room_name = room.name().unwrap_or(NO_NAME_REPR.to_string()); - let rendered_contacts = contacts.borrow_mut().clone().into_iter().map(|room| { - let room_name = room.name().unwrap(); let is_invited = room.matrix_room.state() == RoomState::Invited; + let formatted = format!( "{room_name} - {}", if is_invited { @@ -47,8 +92,6 @@ pub fn ContactsSection<'a>(cx: Scope, name: &'a str, contacts: RefCell } ); - let room_topic = room.topic.unwrap_or(NO_SUBJECT_REPR.to_string()).to_owned(); - rsx!(li { img { src: "./images/status_online.png", @@ -60,8 +103,7 @@ pub fn ContactsSection<'a>(cx: Scope, name: &'a str, contacts: RefCell style: "color: darkgrey;", room_topic, }, - }, - ) + }) }); cx.render(rsx! { diff --git a/src/components/contacts_window/contacts_window.rs b/src/components/contacts_window/contacts_window.rs index 8c57ee7..d16ab12 100644 --- a/src/components/contacts_window/contacts_window.rs +++ b/src/components/contacts_window/contacts_window.rs @@ -1,14 +1,14 @@ use dioxus::prelude::*; -use dioxus_std::utils::rw::UseRw; +use tracing::debug; -use crate::base::Store; use crate::components::contacts_window::contacts::Contacts; use crate::components::contacts_window::user_infos::UserInfos; turf::style_sheet!("src/components/contacts_window/contacts_window.scss"); -#[inline_props] -pub fn ContactsWindow<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { +pub fn ContactsWindow(cx: Scope) -> Element { + debug!("ContactsWindow rendering"); + cx.render(rsx! { style { STYLE_SHEET }, @@ -26,7 +26,7 @@ pub fn ContactsWindow<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { class: ClassName::USER_INFO, }, - UserInfos {rw_store: rw_store}, + UserInfos {}, }, div { @@ -84,7 +84,7 @@ pub fn ContactsWindow<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { }, }, - Contacts {rw_store: rw_store}, + Contacts {}, div { class: ClassName::FOOTER, diff --git a/src/components/contacts_window/user_infos.rs b/src/components/contacts_window/user_infos.rs index ffce9e8..aad1cc1 100644 --- a/src/components/contacts_window/user_infos.rs +++ b/src/components/contacts_window/user_infos.rs @@ -1,7 +1,8 @@ use dioxus::prelude::*; -use dioxus_std::utils::rw::UseRw; +use fermi::*; +use tracing::debug; -use crate::base::Store; +use crate::base::APP_SETTINGS; use crate::components::avatar_selector::AvatarSelector; use crate::components::icons::DownArrowIcon; @@ -9,15 +10,31 @@ turf::style_sheet!("src/components/contacts_window/user_infos.scss"); static MESSAGE_PLACEHOLDER: &str = ""; -#[inline_props] -pub fn UserInfos<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { +pub fn UserInfos(cx: Scope) -> Element { + debug!("UserInfos rendering"); + + let app_settings = use_atom_ref(cx, &APP_SETTINGS); + let store = &app_settings.read().store; + + println!("----------------------------------"); println!("UserInfos rendering"); + // println!("store={:?}", &store); + dbg!(&store.user_id); + println!("----------------------------------"); - let store = rw_store.read().unwrap().clone(); + // let user_id = store.user_id..as_ref().unwrap(); - let user_id = store.user_id.unwrap(); - let user_info = store.user_infos.get(&user_id).unwrap(); - let user_display_name = user_info.display_name.as_ref().unwrap(); + // let mut user_info_option = None; + let mut user_display_name_option = None; + + let user_id_option = &store.user_id; + if user_id_option.is_some() { + let user_id = user_id_option.as_ref().unwrap(); + let user_info_option = store.user_infos.get(user_id); + if user_info_option.is_some() { + user_display_name_option = user_info_option.unwrap().display_name.as_ref(); + } + } cx.render(rsx! { style { STYLE_SHEET }, @@ -37,7 +54,7 @@ pub fn UserInfos<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { class: ClassName::USER_ID, p { class: ClassName::USER_NAME, - "{user_display_name}", + if user_display_name_option.is_some() { "{user_display_name}" } else { "AIE" }, }, p { class: ClassName::USER_STATUS, diff --git a/src/components/login.rs b/src/components/login.rs index 6494fa7..d9078ff 100644 --- a/src/components/login.rs +++ b/src/components/login.rs @@ -1,10 +1,10 @@ use std::str::FromStr; -use std::sync::Arc; use dioxus::prelude::*; -use dioxus_std::utils::rw::UseRw; +use fermi::*; +use tracing::{debug, error}; -use crate::base::{AppSettings, Store}; +use crate::base::APP_SETTINGS; use crate::components::avatar_selector::AvatarSelector; use crate::components::header::Header; use crate::matrix_client::{LoginStyle, MatrixClient}; @@ -13,14 +13,14 @@ turf::style_sheet!("src/components/login.scss"); static EMPTY_PLACEHOLDER: &str = "Tmp placeholder"; -#[inline_props] -pub fn Login<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { - let app_context = use_shared_state::(cx).unwrap(); +pub fn Login(cx: Scope) -> Element { + debug!("Login rendering"); + + let app_settings = use_atom_ref(cx, &APP_SETTINGS); + let invalid_login = use_state(cx, || false); let login = use_ref(cx, || Login::new()); - let arc_store = Arc::new(rw_store.to_owned().clone()); - let password_class = if **invalid_login { ClassName::INVALID_INPUT } else { @@ -29,7 +29,7 @@ pub fn Login<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { let run_matrix_client = move |_| { cx.spawn({ - to_owned![app_context, invalid_login, login, arc_store]; + to_owned![invalid_login, login, app_settings]; let login_ref = login.read(); let homeserver_url = login_ref.homeserver_url.clone().unwrap(); @@ -37,20 +37,22 @@ pub fn Login<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { let password = login_ref.password.clone().unwrap(); async move { - let requester = MatrixClient::spawn(homeserver_url, arc_store.clone()).await; - requester.init(); + let new_matrix_client = MatrixClient::spawn(homeserver_url).await; - match requester.login(LoginStyle::Password(username, password)) { + new_matrix_client.init(); + + match new_matrix_client.login(LoginStyle::Password(username, password)) { Ok(_) => { - println!("successfully logged"); + debug!("successfully logged"); + app_settings.write().store.is_logged = true; } Err(err) => { - println!("Error during login: {err}"); + error!("Error during login: {err}"); invalid_login.modify(|_| true); } } - app_context.write().requester = Some(Arc::new(requester)); + app_settings.write().requester = Some(new_matrix_client); } }); }; diff --git a/src/components/main_window.rs b/src/components/main_window.rs index badc6ec..55f7bfd 100644 --- a/src/components/main_window.rs +++ b/src/components/main_window.rs @@ -1,20 +1,23 @@ use dioxus::prelude::*; -use dioxus_std::utils::rw::UseRw; +use fermi::*; +use tracing::debug; -use crate::base::Store; +use crate::base::APP_SETTINGS; use crate::components::contacts_window::contacts_window::ContactsWindow; use crate::components::login::Login; -#[inline_props] -pub fn MainWindow<'a>(cx: Scope, rw_store: &'a UseRw) -> Element { - let is_logged = rw_store.read().unwrap().is_logged; +pub fn MainWindow(cx: Scope) -> Element { + debug!("MainWindow rendering"); + + let app_settings = use_atom_ref(cx, &APP_SETTINGS); + let is_logged = app_settings.read().store.is_logged; cx.render(rsx! { if is_logged { - rsx!(ContactsWindow {rw_store: rw_store}) + rsx!(ContactsWindow {}) } else { - rsx!(Login {rw_store: rw_store}) + rsx!(Login {}) } }) } diff --git a/src/main.rs b/src/main.rs index 43acc90..9143c36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,45 +2,47 @@ use dioxus::prelude::*; use dioxus_desktop::Config; -use dioxus_std::utils::rw::use_rw; +use fermi::*; +use tracing::{debug, Level}; pub mod components; pub mod matrix_client; -use crate::base::AppSettings; -use crate::base::Store; +use crate::base::APP_SETTINGS; use crate::components::chats_window::chats_window::ChatsWindow; use crate::components::main_window::MainWindow; mod base; -fn App(cx: Scope) -> Element { - use_shared_state_provider(cx, || cx.props.clone()); +fn App(cx: Scope) -> Element { + debug!("App rendering"); - let rw_store = use_rw(cx, || Store::new()); + use_init_atom_root(cx); - let is_logged = rw_store.read().unwrap().is_logged; + let app_settings = use_atom_ref(cx, &APP_SETTINGS); + let is_logged = app_settings.read().store.is_logged; let chats_win_state = use_state(cx, || None); if is_logged && chats_win_state.is_none() { let chats_window = dioxus_desktop::use_window(cx); + let chats_dom = VirtualDom::new(ChatsWindow); let window_cfg = Config::default().with_custom_head( r#" - -"# + margin: 0; + } + #main, #bodywrap { + height: 100%; + width: 100%; + } + + "# .to_owned(), ); let chats_window_desktop_service = chats_window.new_window(chats_dom, window_cfg); @@ -48,17 +50,19 @@ fn App(cx: Scope) -> Element { } cx.render(rsx! { - MainWindow {rw_store: rw_store} + MainWindow {} }) } #[tokio::main] async fn main() -> anyhow::Result<()> { - tracing_subscriber::fmt::init(); + tracing_subscriber::fmt() + // .pretty() + .with_max_level(Level::DEBUG) + .init(); - let app_settings = AppSettings::new(); - - dioxus_desktop::launch_with_props(App, app_settings, Config::default()); + dioxus_desktop::launch(App); + // dioxus_web::launch(App); Ok(()) } diff --git a/src/matrix_client.rs b/src/matrix_client.rs index 5330232..bf3aebc 100644 --- a/src/matrix_client.rs +++ b/src/matrix_client.rs @@ -1,42 +1,43 @@ +// TODO: make a choice: mpsc vs flume. + use std::fmt::{Debug, Formatter}; use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; use std::time::Duration; -use dioxus_std::utils::rw::UseRw; +use dioxus::prelude::to_owned; +use flume::{bounded, unbounded}; +use flume::{Receiver as FlumeReceiver, Sender}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::task::JoinHandle; +use tracing::{debug, error, warn}; use matrix_sdk::{ config::SyncSettings, - event_handler::Ctx, room::Room as MatrixRoom, - ruma::{ - events::{ - key::verification::{ - done::{OriginalSyncKeyVerificationDoneEvent, ToDeviceKeyVerificationDoneEvent}, - key::{OriginalSyncKeyVerificationKeyEvent, ToDeviceKeyVerificationKeyEvent}, - request::ToDeviceKeyVerificationRequestEvent, - start::{OriginalSyncKeyVerificationStartEvent, ToDeviceKeyVerificationStartEvent}, - }, - presence::PresenceEvent, - reaction::ReactionEventContent, - room::{ - member::{OriginalSyncRoomMemberEvent, RoomMemberEventContent}, - message::RoomMessageEventContent, - name::RoomNameEventContent, - redaction::OriginalSyncRoomRedactionEvent, - topic::RoomTopicEventContent, - }, - typing::SyncTypingEvent, - SyncMessageLikeEvent, SyncStateEvent, + ruma::events::{ + key::verification::{ + done::{OriginalSyncKeyVerificationDoneEvent, ToDeviceKeyVerificationDoneEvent}, + key::{OriginalSyncKeyVerificationKeyEvent, ToDeviceKeyVerificationKeyEvent}, + request::ToDeviceKeyVerificationRequestEvent, + start::{OriginalSyncKeyVerificationStartEvent, ToDeviceKeyVerificationStartEvent}, }, - OwnedRoomId, OwnedUserId, + presence::PresenceEvent, + reaction::ReactionEventContent, + room::{ + member::{OriginalSyncRoomMemberEvent, RoomMemberEventContent}, + message::RoomMessageEventContent, + name::RoomNameEventContent, + redaction::OriginalSyncRoomRedactionEvent, + topic::RoomTopicEventContent, + }, + typing::SyncTypingEvent, + SyncMessageLikeEvent, SyncStateEvent, }, - Client, DisplayName, RoomMemberships, + Client, }; -use crate::base::{ByIdRooms, ReactiveStore, Room, Store, UserInfo}; +use crate::base::Room; #[derive(Debug)] pub enum LoginStyle { @@ -104,53 +105,183 @@ fn oneshot() -> (ClientReply, ClientResponse) { pub struct Requester { pub client: Arc, pub tx: UnboundedSender, + pub rooms_receiver: flume::Receiver, } impl Requester { pub fn init(&self) { - println!("Requester.init BEG"); let (reply, response) = oneshot(); - self.tx.send(WorkerTask::Init(reply)).unwrap(); - - println!("Requester.init END"); return response.recv(); } pub fn login(&self, style: LoginStyle) -> anyhow::Result<()> { - println!("Requester.login BEG"); let (reply, response) = oneshot(); - self.tx.send(WorkerTask::Login(style, reply)).unwrap(); - - println!("Requester.login END"); return response.recv(); } } pub struct MatrixClient { initialized: bool, - client: Arc, - // sync_token: Option, + client: Option>, load_handle: Option>, sync_handle: Option>, - store: ReactiveStore, + rooms_sender: Option>, } impl MatrixClient { - fn new(store: ReactiveStore, client: Arc) -> Self { + pub fn new(client: Arc, rooms_sender: flume::Sender) -> Self { Self { - client: client, initialized: false, - // sync_token: None, + client: Some(client), load_handle: None, sync_handle: None, - store: store, + rooms_sender: Some(rooms_sender), } } - pub async fn spawn(homeserver_url: String, store: ReactiveStore) -> Requester { + async fn on_sync_typing_event(_ev: SyncTypingEvent, room: MatrixRoom) { + debug!("== on_sync_typing_event =="); + let room_id = room.room_id().to_owned(); + dbg!(room_id); + } + + async fn on_presence_event(_ev: PresenceEvent) { + debug!("== on_presence_event =="); + dbg!(_ev); + } + + async fn on_sync_state_event(_ev: SyncStateEvent, _room: MatrixRoom) { + debug!("== on_sync_state_event =="); + dbg!(_ev); + } + + async fn on_room_topic_event(ev: SyncStateEvent, _room: MatrixRoom) { + debug!("== on_room_topic_event =="); + dbg!(&ev); + // if let SyncStateEvent::Original(ev) = ev { + // let room_id = room.room_id().to_owned(); + // let store = reactive_store.read().unwrap().to_owned(); + + // if let Some(store_room) = store.rooms.get(&room_id) { + // // store_room.write().unwrap().topic = Some(ev.content.topic); + // // let _ = reactive_store.write(store); + // println!("HOP"); + // } else { + // println!("No room with \"{room_id}\" id known"); + // } + // } + } + + async fn on_room_member_event(ev: SyncStateEvent, _room: MatrixRoom) { + debug!("== on_room_member_event =="); + dbg!(ev); + // // dbg!(room); + // if room.invited_members_count() > 0 { + // dbg!(room); + // } + // if let SyncStateEvent::Original(ev) = ev {} + } + + async fn on_sync_message_like_room_message_event( + ev: SyncMessageLikeEvent, + _room: MatrixRoom, + _client: Client, + ) { + debug!("== on_sync_message_like_room_message_event =="); + dbg!(ev); + } + + async fn on_sync_message_like_reaction_event( + ev: SyncMessageLikeEvent, + _room: MatrixRoom, + ) { + debug!("== on_sync_message_like_reaction_event =="); + dbg!(ev); + } + + async fn on_original_sync_room_redaction_event( + ev: OriginalSyncRoomRedactionEvent, + _room: MatrixRoom, + ) { + debug!("== on_original_sync_room_redaction_event =="); + dbg!(ev); + } + + async fn on_original_sync_room_member_event( + ev: OriginalSyncRoomMemberEvent, + room: MatrixRoom, + _client: Client, + ) { + debug!("== on_original_sync_room_member_event =="); + dbg!(ev); + let room_id = room.room_id(); + dbg!(room_id); + + // let mut store = store_ctx.read().unwrap().to_owned(); + // dbg!(store.rooms.keys()); + // let is_direct = room.is_direct().await.ok(); + // store.rooms.insert( + // OwnedRoomId::from(room_id), + // Arc::new(RwLock::new(Room::new(Arc::new(room), None, is_direct))), + // ); + // let _ = store_ctx.write(store); + } + + async fn on_original_sync_key_verif_start_event( + ev: OriginalSyncKeyVerificationStartEvent, + _client: Client, + ) { + debug!("== on_original_sync_key_verif_start_event =="); + dbg!(ev); + } + + async fn on_original_sync_key_verif_key_event( + ev: OriginalSyncKeyVerificationKeyEvent, + _client: Client, + ) { + debug!("== on_original_sync_key_verif_key_event =="); + dbg!(ev); + } + + async fn on_original_sync_key_verif_done_event( + ev: OriginalSyncKeyVerificationDoneEvent, + _client: Client, + ) { + debug!("== on_original_sync_key_verif_done_event =="); + dbg!(ev); + } + + async fn on_device_key_verif_req_event( + ev: ToDeviceKeyVerificationRequestEvent, + _client: Client, + ) { + debug!("== on_device_key_verif_req_event =="); + dbg!(ev); + } + + async fn on_device_key_verif_start_event( + ev: ToDeviceKeyVerificationStartEvent, + _client: Client, + ) { + debug!("== on_device_key_verif_start_event =="); + dbg!(ev); + } + + async fn on_device_key_verif_key_event(ev: ToDeviceKeyVerificationKeyEvent, _client: Client) { + debug!("== on_device_key_verif_key_event =="); + dbg!(ev); + } + + async fn on_device_key_verif_done_event(ev: ToDeviceKeyVerificationDoneEvent, _client: Client) { + debug!("== on_device_key_verif_done_event =="); + dbg!(ev); + } + + pub async fn spawn(homeserver_url: String) -> Requester { let (tx, rx) = unbounded_channel::(); + let (rooms_sender, rooms_receiver) = unbounded::(); let client = Arc::new( Client::builder() @@ -160,7 +291,7 @@ impl MatrixClient { .unwrap(), ); - let mut matrix_client = MatrixClient::new(store, client.clone()); + let mut matrix_client = MatrixClient::new(client.clone(), rooms_sender); tokio::spawn({ async move { @@ -168,209 +299,18 @@ impl MatrixClient { } }); - Requester { client, tx } - } - - async fn work(&mut self, mut rx: UnboundedReceiver) { - println!("MatrixClient.work BEG"); - loop { - let task = rx.recv().await; - println!("task={:?}", task); - - match task { - Some(task) => self.run(task).await, - None => { - break; - } - } - } - - println!("MatrixClient.work END"); - - if let Some(handle) = self.sync_handle.take() { - handle.abort(); + Requester { + client, + tx, + rooms_receiver, } } - async fn run(&mut self, task: WorkerTask) { - println!("Run({:?}", task); - match task { - WorkerTask::Init(reply) => { - assert_eq!(self.initialized, false); - self.init().await; - reply.send(()); - } - WorkerTask::Login(style, reply) => { - assert!(self.initialized); - reply.send(self.login_and_sync(style).await); - } - } - } + fn init(&mut self) { + let client = self.client.clone().unwrap(); - async fn on_sync_typing_event( - _ev: SyncTypingEvent, - room: MatrixRoom, - _store: Ctx, - ) { - println!("== on_sync_typing_event =="); - let room_id = room.room_id().to_owned(); - // dbg!(room_id); - } - - async fn on_presence_event(_ev: PresenceEvent, _store: Ctx) { - println!("== on_presence_event =="); - dbg!(_ev); - } - - async fn on_sync_state_event( - _ev: SyncStateEvent, - _room: MatrixRoom, - _store: Ctx, - ) { - println!("== on_sync_state_event =="); - dbg!(_ev); - } - - async fn on_room_topic_event( - ev: SyncStateEvent, - room: MatrixRoom, - reactive_store: Ctx, - ) { - dbg!(&ev); - if let SyncStateEvent::Original(ev) = ev { - let room_id = room.room_id().to_owned(); - let store = reactive_store.read().unwrap().to_owned(); - - if let Some(store_room) = store.rooms.get(&room_id) { - store_room.write().unwrap().topic = Some(ev.content.topic); - let _ = reactive_store.write(store); - println!("HOP"); - } else { - println!("No room with \"{room_id}\" id known"); - } - } - } - - async fn on_room_member_event( - ev: SyncStateEvent, - room: MatrixRoom, - store_ctx: Ctx, - ) { - println!("== on_room_member_event =="); - // // dbg!(ev); - // // dbg!(room); - // if room.invited_members_count() > 0 { - // dbg!(room); - // } - // if let SyncStateEvent::Original(ev) = ev {} - } - - async fn on_sync_message_like_room_message_event( - _ev: SyncMessageLikeEvent, - _room: MatrixRoom, - _client: Client, - _store: Ctx, - ) { - println!("== on_sync_message_like_room_message_event =="); - // dbg!(_ev); - } - - async fn on_sync_message_like_reaction_event( - _ev: SyncMessageLikeEvent, - _room: MatrixRoom, - _store: Ctx, - ) { - println!("== on_sync_message_like_reaction_event =="); - } - - async fn on_original_sync_room_redaction_event( - _ev: OriginalSyncRoomRedactionEvent, - _room: MatrixRoom, - _store: Ctx, - ) { - println!("== on_original_sync_room_redaction_event =="); - } - - async fn on_original_sync_room_member_event( - _ev: OriginalSyncRoomMemberEvent, - room: MatrixRoom, - _client: Client, - store_ctx: Ctx, - ) { - println!("== on_original_sync_room_member_event =="); - let room_id = room.room_id(); - dbg!(room_id); - - let mut store = store_ctx.read().unwrap().to_owned(); - dbg!(store.rooms.keys()); - let is_direct = room.is_direct().await.ok(); - store.rooms.insert( - OwnedRoomId::from(room_id), - Arc::new(RwLock::new(Room::new(Arc::new(room), None, is_direct))), - ); - let _ = store_ctx.write(store); - } - - async fn on_original_sync_key_verif_start_event( - _ev: OriginalSyncKeyVerificationStartEvent, - _client: Client, - _store: Ctx, - ) { - println!("== on_original_sync_key_verif_start_event =="); - } - - async fn on_original_sync_key_verif_key_event( - _ev: OriginalSyncKeyVerificationKeyEvent, - _client: Client, - _store: Ctx, - ) { - println!("== on_original_sync_key_verif_key_event =="); - } - - async fn on_original_sync_key_verif_done_event( - _ev: OriginalSyncKeyVerificationDoneEvent, - _client: Client, - _store: Ctx, - ) { - println!("== on_original_sync_key_verif_done_event =="); - } - - async fn on_device_key_verif_req_event( - _ev: ToDeviceKeyVerificationRequestEvent, - _client: Client, - _store: Ctx, - ) { - println!("== on_device_key_verif_req_event =="); - } - - async fn on_device_key_verif_start_event( - _ev: ToDeviceKeyVerificationStartEvent, - _client: Client, - _store: Ctx, - ) { - println!("== on_device_key_verif_start_event =="); - } - - async fn on_device_key_verif_key_event( - _ev: ToDeviceKeyVerificationKeyEvent, - _client: Client, - _store: Ctx, - ) { - println!("== on_device_key_verif_key_event =="); - } - - async fn on_device_key_verif_done_event( - _ev: ToDeviceKeyVerificationDoneEvent, - _client: Client, - _store: Ctx, - ) { - println!("== on_device_key_verif_done_event =="); - } - - async fn init(&mut self) { - let client = self.client.clone(); - let store = self.store.clone(); - client.add_event_handler_context(store); + // let store = self.store.clone(); + // client.add_event_handler_context(store); let _ = client.add_event_handler(MatrixClient::on_sync_typing_event); let _ = client.add_event_handler(MatrixClient::on_presence_event); @@ -389,107 +329,42 @@ impl MatrixClient { let _ = client.add_event_handler(MatrixClient::on_room_topic_event); let _ = client.add_event_handler(MatrixClient::on_room_member_event); - self.load_handle = tokio::spawn({ - let client = self.client.clone(); - let store = self.store.clone(); - - async move { - let rooms_refresh = Self::refresh_rooms_forever(client.as_ref(), store.as_ref()); - let ((),) = tokio::join!(rooms_refresh); - } - }) - .into(); - self.initialized = true; } - async fn load_user_infos(&self) { - let mut store = self.store.read().unwrap().to_owned(); + async fn refresh_rooms(client: &Client, rooms_sender: &Sender) { + let joined_matrix_rooms_ref = &client.joined_rooms(); + let invited_matrix_rooms_ref = &client.invited_rooms(); - let user_id = self.client.user_id().unwrap(); - store.user_id = Some(OwnedUserId::from(user_id)); - - let res = self.client.account().get_profile().await.unwrap(); - let user_info = UserInfo::new(res.avatar_url, res.displayname, res.blurhash); - - store - .user_infos - .insert(OwnedUserId::from(user_id), user_info); - - let _ = self.store.write(store); - } - - async fn get_formatted_room_name(room: &MatrixRoom) -> String { - room.display_name() - .await - .unwrap_or(DisplayName::Empty) - .to_string() - } - - async fn refresh_rooms(client: &Client, store: &UseRw) { - // println!("== refresh_rooms =="); - - let mut _store = store.read().unwrap().to_owned(); - - let mut new_rooms = ByIdRooms::new(); - - for room in &client.joined_rooms() { - let room_id = room.room_id(); - - let members = room.members(RoomMemberships::empty()).await.unwrap(); - if !_store.rooms.contains_key(room_id) { - let is_direct = room.is_direct().await.ok(); - new_rooms.insert( - OwnedRoomId::from(room_id), - Arc::new(RwLock::new(Room::new( - Arc::new(room.to_owned()), - None, - is_direct, - ))), + for matrix_rooms in vec![joined_matrix_rooms_ref, invited_matrix_rooms_ref] { + for matrix_room in matrix_rooms.iter() { + let room = Room::new( + Arc::new(matrix_room.to_owned()), + None, + matrix_room.is_direct().await.ok(), ); + if let Err(err) = rooms_sender.send_async(room).await { + warn!("Error: {}", err); + } } } - - for room in client.invited_rooms().into_iter() { - let room_id = room.room_id(); - - if !_store.rooms.contains_key(room_id) { - let is_direct = room.is_direct().await.ok(); - new_rooms.insert( - OwnedRoomId::from(room_id), - Arc::new(RwLock::new(Room::new( - Arc::new(room.to_owned()), - None, - is_direct, - ))), - ); - } - } - - let mut updated = false; - for (room_id, room) in new_rooms.into_iter() { - updated = true; - _store.rooms.insert(room_id, room); - } - - if updated { - let _ = store.write(_store); - } } - async fn refresh_rooms_forever(client: &Client, store: &UseRw) { + async fn refresh_rooms_forever(client: &Client, rooms_channel: &Sender) { // TODO: Add interval to config let mut interval = tokio::time::interval(Duration::from_secs(5)); loop { - Self::refresh_rooms(client, store).await; + Self::refresh_rooms(client, rooms_channel).await; + error!("== Interval tick BEG =="); interval.tick().await; + error!("== Interval tick END =="); } } async fn login_and_sync(&mut self, style: LoginStyle) -> anyhow::Result<()> { - let client = self.client.clone(); + let client = self.client.clone().unwrap(); match style { LoginStyle::Password(username, password) => { @@ -503,13 +378,19 @@ impl MatrixClient { } } - self.load_user_infos().await; + let (synchronized_tx, synchronized_rx) = bounded(1); self.sync_handle = tokio::spawn({ async move { // Sync once so we receive the client state and old messages let _ = client.sync_once(SyncSettings::default()).await; + debug!("User connected to the homeserver"); + + if let Err(err) = synchronized_tx.send(true) { + warn!("Unable to notify that the Matrix client is now synchronized ({err})"); + } + loop { let settings = SyncSettings::default(); let _ = client.sync(settings).await; @@ -518,11 +399,61 @@ impl MatrixClient { }) .into(); - let mut store = self.store.read().unwrap().to_owned(); - store.is_logged = true; - let _ = self.store.write(store); + self.start_background_tasks(synchronized_rx); - println!("User connected to the homeserver"); Ok(()) } + + fn start_background_tasks(&mut self, synchronized_rx: FlumeReceiver) { + let client = self.client.clone().unwrap(); + let rooms_sender_ref = &self.rooms_sender; + + self.load_handle = tokio::spawn({ + to_owned![rooms_sender_ref]; + + async move { + if let Err(err) = synchronized_rx.recv() { + error!("Unable to setup the rx channel notifying that the Matrix client is now synchronized ({err})"); + } + + let rooms_refresh = Self::refresh_rooms_forever( + client.as_ref(), + rooms_sender_ref.as_ref().unwrap(), + ); + let ((),) = tokio::join!(rooms_refresh); + } + }) + .into(); + } + + async fn work(&mut self, mut rx: UnboundedReceiver) { + loop { + let task = rx.recv().await; + + match task { + Some(task) => self.run(task).await, + None => { + break; + } + } + } + + if let Some(handle) = self.sync_handle.take() { + handle.abort(); + } + } + + async fn run(&mut self, task: WorkerTask) { + match task { + WorkerTask::Init(reply) => { + assert_eq!(self.initialized, false); + self.init(); + reply.send(()); + } + WorkerTask::Login(style, reply) => { + assert!(self.initialized); + reply.send(self.login_and_sync(style).await); + } + } + } }