🏗️ Rearchitecting the interface with the MatrixClient

- Replace RwStore with channels.
- Use of fermi to handle application data.
- Use of tracing.
This commit is contained in:
2023-12-10 22:01:05 +01:00
parent 4988054dae
commit ae8dba86f6
11 changed files with 472 additions and 450 deletions

View File

@@ -6,20 +6,27 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dioxus = "0.4.0" dioxus = "0.4.3"
dioxus-desktop = "0.4.0" 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 = { version = "0.6.2", features = ["js"] }
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", branch = "main" , 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" anyhow = "1.0.75"
tokio = "1.29.1" url = "2.5.0"
dirs = "5.0.1" dirs = "5.0.1"
ctrlc-async = "3.2.2" ctrlc-async = "3.2.2"
tracing-subscriber = "0.3.17" tracing-subscriber = "0.3.18"
dioxus-free-icons = { version = "0.7.0", features = ["material-design-icons-navigation", "ionicons"] } thiserror = "1.0.50"
thiserror = "1.0.44" turf = "0.7.0"
turf = "0.5.0" tokio = "1.34.0"
dioxus-std = { version = "0.4.0", features = ["utils"] } flume = "0.11.0"
log = "0.4.20"
tracing = "0.1.40"
[build] [build]
target = "x86_64-unknown-linux-gnu" target = "x86_64-unknown-linux-gnu"

View File

@@ -1,9 +1,6 @@
use std::{ use std::{cell::RefCell, collections::HashMap, sync::Arc};
collections::HashMap,
sync::{Arc, RwLock},
};
use dioxus_std::utils::rw::UseRw; use fermi::*;
use matrix_sdk::room::Room as MatrixRoom; use matrix_sdk::room::Room as MatrixRoom;
use matrix_sdk::{ use matrix_sdk::{
room::RoomMember, room::RoomMember,
@@ -36,7 +33,7 @@ impl UserInfo {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Room { pub struct Room {
pub matrix_room: Arc<MatrixRoom>, pub matrix_room: Arc<MatrixRoom>,
pub topic: Option<String>, pub topic: Option<RefCell<String>>,
pub members: HashMap<OwnedUserId, RoomMember>, pub members: HashMap<OwnedUserId, RoomMember>,
pub is_direct: Option<bool>, pub is_direct: Option<bool>,
} }
@@ -44,7 +41,7 @@ pub struct Room {
impl Room { impl Room {
pub fn new( pub fn new(
matrix_room: Arc<MatrixRoom>, matrix_room: Arc<MatrixRoom>,
topic: Option<String>, topic: Option<RefCell<String>>,
is_direct: Option<bool>, is_direct: Option<bool>,
) -> Self { ) -> Self {
Self { Self {
@@ -58,21 +55,23 @@ impl Room {
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
self.matrix_room.name() self.matrix_room.name()
} }
pub fn id(&self) -> OwnedRoomId {
OwnedRoomId::from(self.matrix_room.room_id())
}
} }
impl PartialEq for Room { impl PartialEq for Room {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// TODO: Look for a better way to compare Matrix rooms // TODO: Look for a better way to compare Matrix rooms
self.matrix_room.room_id() == other.matrix_room.room_id() self.matrix_room.room_id() == other.matrix_room.room_id()
&& self.topic == other.topic
&& self.is_direct == other.is_direct
} }
} }
pub type ByIdRooms = HashMap<OwnedRoomId, Arc<RwLock<Room>>>; pub type ByIdRooms = HashMap<OwnedRoomId, RefCell<Room>>;
pub type ByIdUserInfos = HashMap<OwnedUserId, UserInfo>; pub type ByIdUserInfos = HashMap<OwnedUserId, UserInfo>;
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct Store { pub struct Store {
pub is_logged: bool, pub is_logged: bool,
pub rooms: ByIdRooms, pub rooms: ByIdRooms,
@@ -80,7 +79,7 @@ pub struct Store {
pub user_id: Option<OwnedUserId>, pub user_id: Option<OwnedUserId>,
} }
impl<'a> Store { impl Store {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
is_logged: false, is_logged: false,
@@ -107,15 +106,19 @@ impl PartialEq for Store {
impl Eq for Store {} impl Eq for Store {}
pub type ReactiveStore = Arc<UseRw<Store>>;
#[derive(Clone)]
pub struct AppSettings { pub struct AppSettings {
pub requester: Option<Arc<Requester>>, pub requester: Option<Requester>,
pub store: Store,
} }
impl AppSettings { impl AppSettings {
pub fn new() -> Self { pub fn new() -> Self {
Self { requester: None } Self {
requester: None,
store: Store::new(),
} }
} }
}
pub static APP_SETTINGS: AtomRef<AppSettings> = AtomRef(|_| AppSettings::new());
pub static ROOMS: AtomRef<ByIdRooms> = AtomRef(|_| ByIdRooms::new());

View File

@@ -1,4 +1,5 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use tracing::debug;
use crate::components::avatar_selector::AvatarSelector; use crate::components::avatar_selector::AvatarSelector;
use crate::components::chats_window::edit_section::EditSection; 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"); turf::style_sheet!("src/components/chats_window/chats_window.scss");
pub fn ChatsWindow(cx: Scope) -> Element { pub fn ChatsWindow(cx: Scope) -> Element {
debug!("ChatsWindow rendering");
let room_names = vec![ let room_names = vec![
"MON POTE", "MON POTE",
"Second room", "Second room",

View File

@@ -1,36 +1,46 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::time::Duration;
use dioxus::prelude::*; 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::AppSettings;
use crate::base::Store; use crate::base::{ByIdRooms, Room, APP_SETTINGS, ROOMS};
use crate::components::contacts_window::contacts_section::ContactsSection; use crate::components::contacts_window::contacts_section::{
filter_people_conversations, filter_room_conversations, ContactsSection,
};
turf::style_sheet!("src/components/contacts_window/contacts.scss"); turf::style_sheet!("src/components/contacts_window/contacts.scss");
#[inline_props] pub fn Contacts(cx: Scope) -> Element {
pub fn Contacts<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element { debug!("Contacts rendering");
println!("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<AppSettings>, rooms_atom: UseAtomRef<ByIdRooms>) {
loop {
let requester = &app_settings.read().requester;
let rooms_len = rooms.len(); if requester.is_some() {
let mut groups = Vec::<Room>::with_capacity(rooms_len); let rooms_receiver = &requester.as_ref().unwrap().rooms_receiver;
let mut directs = Vec::<Room>::with_capacity(rooms_len); let room = rooms_receiver.recv_async().await.unwrap();
let room_id = room.id();
for arc_room in rooms.values() { dbg!(&room_id);
let room = arc_room.read().unwrap().to_owned(); rooms_atom
.write()
let is_direct = room.is_direct.unwrap(); .insert(room_id, RefCell::<Room>::new(room));
if is_direct {
directs.push(room);
} else { } else {
groups.push(room); sleep(Duration::from_millis(1000)).await;
} }
} }
}
use_coroutine(cx, |_: UnboundedReceiver<()>| {
sync_rooms(app_settings_atom.clone(), rooms_atom.clone())
});
// TODO: Test overflow // TODO: Test overflow
// TODO: Add offline users ? // TODO: Add offline users ?
@@ -40,8 +50,8 @@ pub fn Contacts<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element {
div { div {
class: ClassName::CONTACTS, class: ClassName::CONTACTS,
ContactsSection {name: "Groups", contacts: RefCell::new(groups)}, ContactsSection {name: "Groups", filter: &filter_room_conversations},
ContactsSection {name: "Available", contacts: RefCell::new(directs)}, ContactsSection {name: "Available", filter: &filter_people_conversations},
}, },
}) })
} }

View File

@@ -3,9 +3,11 @@ use std::cell::RefCell;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_free_icons::icons::io_icons::IoChevronDown; use dioxus_free_icons::icons::io_icons::IoChevronDown;
use dioxus_free_icons::Icon; use dioxus_free_icons::Icon;
use fermi::prelude::*;
use matrix_sdk::RoomState; 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"); 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"; static NO_SUBJECT_REPR: &str = "No subject";
#[inline_props] pub fn filter_people_conversations(rooms_atom: UseAtomRef<ByIdRooms>) -> Vec<RefCell<Room>> {
pub fn ContactsSection<'a>(cx: Scope, name: &'a str, contacts: RefCell<Vec<Room>>) -> Element { let rooms = rooms_atom.read();
println!("ContactsSection rendering"); let mut filtered_rooms = Vec::<RefCell<Room>>::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<ByIdRooms>) -> Vec<RefCell<Room>> {
let rooms = rooms_atom.read();
let mut filtered_rooms = Vec::<RefCell<Room>>::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<ByIdRooms>) -> Vec<RefCell<Room>>,
) -> 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); let show = use_state(cx, || false);
@@ -33,11 +70,19 @@ pub fn ContactsSection<'a>(cx: Scope, name: &'a str, contacts: RefCell<Vec<Room>
] ]
.join(" "); .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 is_invited = room.matrix_room.state() == RoomState::Invited;
let formatted = format!( let formatted = format!(
"{room_name} - {}", "{room_name} - {}",
if is_invited { if is_invited {
@@ -47,8 +92,6 @@ pub fn ContactsSection<'a>(cx: Scope, name: &'a str, contacts: RefCell<Vec<Room>
} }
); );
let room_topic = room.topic.unwrap_or(NO_SUBJECT_REPR.to_string()).to_owned();
rsx!(li { rsx!(li {
img { img {
src: "./images/status_online.png", src: "./images/status_online.png",
@@ -60,8 +103,7 @@ pub fn ContactsSection<'a>(cx: Scope, name: &'a str, contacts: RefCell<Vec<Room>
style: "color: darkgrey;", style: "color: darkgrey;",
room_topic, room_topic,
}, },
}, })
)
}); });
cx.render(rsx! { cx.render(rsx! {

View File

@@ -1,14 +1,14 @@
use dioxus::prelude::*; 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::contacts::Contacts;
use crate::components::contacts_window::user_infos::UserInfos; use crate::components::contacts_window::user_infos::UserInfos;
turf::style_sheet!("src/components/contacts_window/contacts_window.scss"); turf::style_sheet!("src/components/contacts_window/contacts_window.scss");
#[inline_props] pub fn ContactsWindow(cx: Scope) -> Element {
pub fn ContactsWindow<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element { debug!("ContactsWindow rendering");
cx.render(rsx! { cx.render(rsx! {
style { STYLE_SHEET }, style { STYLE_SHEET },
@@ -26,7 +26,7 @@ pub fn ContactsWindow<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element {
class: ClassName::USER_INFO, class: ClassName::USER_INFO,
}, },
UserInfos {rw_store: rw_store}, UserInfos {},
}, },
div { div {
@@ -84,7 +84,7 @@ pub fn ContactsWindow<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element {
}, },
}, },
Contacts {rw_store: rw_store}, Contacts {},
div { div {
class: ClassName::FOOTER, class: ClassName::FOOTER,

View File

@@ -1,7 +1,8 @@
use dioxus::prelude::*; 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::avatar_selector::AvatarSelector;
use crate::components::icons::DownArrowIcon; use crate::components::icons::DownArrowIcon;
@@ -9,15 +10,31 @@ turf::style_sheet!("src/components/contacts_window/user_infos.scss");
static MESSAGE_PLACEHOLDER: &str = "<Enter a personal message>"; static MESSAGE_PLACEHOLDER: &str = "<Enter a personal message>";
#[inline_props] pub fn UserInfos(cx: Scope) -> Element {
pub fn UserInfos<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element { debug!("UserInfos rendering");
let app_settings = use_atom_ref(cx, &APP_SETTINGS);
let store = &app_settings.read().store;
println!("----------------------------------");
println!("UserInfos rendering"); 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 mut user_info_option = None;
let user_info = store.user_infos.get(&user_id).unwrap(); let mut user_display_name_option = None;
let user_display_name = user_info.display_name.as_ref().unwrap();
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! { cx.render(rsx! {
style { STYLE_SHEET }, style { STYLE_SHEET },
@@ -37,7 +54,7 @@ pub fn UserInfos<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element {
class: ClassName::USER_ID, class: ClassName::USER_ID,
p { p {
class: ClassName::USER_NAME, class: ClassName::USER_NAME,
"{user_display_name}", if user_display_name_option.is_some() { "{user_display_name}" } else { "AIE" },
}, },
p { p {
class: ClassName::USER_STATUS, class: ClassName::USER_STATUS,

View File

@@ -1,10 +1,10 @@
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use dioxus::prelude::*; 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::avatar_selector::AvatarSelector;
use crate::components::header::Header; use crate::components::header::Header;
use crate::matrix_client::{LoginStyle, MatrixClient}; use crate::matrix_client::{LoginStyle, MatrixClient};
@@ -13,14 +13,14 @@ turf::style_sheet!("src/components/login.scss");
static EMPTY_PLACEHOLDER: &str = "Tmp placeholder"; static EMPTY_PLACEHOLDER: &str = "Tmp placeholder";
#[inline_props] pub fn Login(cx: Scope) -> Element {
pub fn Login<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element { debug!("Login rendering");
let app_context = use_shared_state::<AppSettings>(cx).unwrap();
let app_settings = use_atom_ref(cx, &APP_SETTINGS);
let invalid_login = use_state(cx, || false); let invalid_login = use_state(cx, || false);
let login = use_ref(cx, || Login::new()); let login = use_ref(cx, || Login::new());
let arc_store = Arc::new(rw_store.to_owned().clone());
let password_class = if **invalid_login { let password_class = if **invalid_login {
ClassName::INVALID_INPUT ClassName::INVALID_INPUT
} else { } else {
@@ -29,7 +29,7 @@ pub fn Login<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element {
let run_matrix_client = move |_| { let run_matrix_client = move |_| {
cx.spawn({ cx.spawn({
to_owned![app_context, invalid_login, login, arc_store]; to_owned![invalid_login, login, app_settings];
let login_ref = login.read(); let login_ref = login.read();
let homeserver_url = login_ref.homeserver_url.clone().unwrap(); let homeserver_url = login_ref.homeserver_url.clone().unwrap();
@@ -37,20 +37,22 @@ pub fn Login<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element {
let password = login_ref.password.clone().unwrap(); let password = login_ref.password.clone().unwrap();
async move { async move {
let requester = MatrixClient::spawn(homeserver_url, arc_store.clone()).await; let new_matrix_client = MatrixClient::spawn(homeserver_url).await;
requester.init();
match requester.login(LoginStyle::Password(username, password)) { new_matrix_client.init();
match new_matrix_client.login(LoginStyle::Password(username, password)) {
Ok(_) => { Ok(_) => {
println!("successfully logged"); debug!("successfully logged");
app_settings.write().store.is_logged = true;
} }
Err(err) => { Err(err) => {
println!("Error during login: {err}"); error!("Error during login: {err}");
invalid_login.modify(|_| true); invalid_login.modify(|_| true);
} }
} }
app_context.write().requester = Some(Arc::new(requester)); app_settings.write().requester = Some(new_matrix_client);
} }
}); });
}; };

View File

@@ -1,20 +1,23 @@
use dioxus::prelude::*; 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::contacts_window::contacts_window::ContactsWindow;
use crate::components::login::Login; use crate::components::login::Login;
#[inline_props] pub fn MainWindow(cx: Scope) -> Element {
pub fn MainWindow<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> Element { debug!("MainWindow rendering");
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;
cx.render(rsx! { cx.render(rsx! {
if is_logged { if is_logged {
rsx!(ContactsWindow {rw_store: rw_store}) rsx!(ContactsWindow {})
} }
else { else {
rsx!(Login {rw_store: rw_store}) rsx!(Login {})
} }
}) })
} }

View File

@@ -2,29 +2,31 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::Config; use dioxus_desktop::Config;
use dioxus_std::utils::rw::use_rw; use fermi::*;
use tracing::{debug, Level};
pub mod components; pub mod components;
pub mod matrix_client; pub mod matrix_client;
use crate::base::AppSettings; use crate::base::APP_SETTINGS;
use crate::base::Store;
use crate::components::chats_window::chats_window::ChatsWindow; use crate::components::chats_window::chats_window::ChatsWindow;
use crate::components::main_window::MainWindow; use crate::components::main_window::MainWindow;
mod base; mod base;
fn App(cx: Scope<AppSettings>) -> Element { fn App(cx: Scope) -> Element {
use_shared_state_provider(cx, || cx.props.clone()); 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); let chats_win_state = use_state(cx, || None);
if is_logged && chats_win_state.is_none() { if is_logged && chats_win_state.is_none() {
let chats_window = dioxus_desktop::use_window(cx); let chats_window = dioxus_desktop::use_window(cx);
let chats_dom = VirtualDom::new(ChatsWindow); let chats_dom = VirtualDom::new(ChatsWindow);
let window_cfg = Config::default().with_custom_head( let window_cfg = Config::default().with_custom_head(
r#" r#"
@@ -48,17 +50,19 @@ fn App(cx: Scope<AppSettings>) -> Element {
} }
cx.render(rsx! { cx.render(rsx! {
MainWindow {rw_store: rw_store} MainWindow {}
}) })
} }
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { 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(App);
// dioxus_web::launch(App);
dioxus_desktop::launch_with_props(App, app_settings, Config::default());
Ok(()) Ok(())
} }

View File

@@ -1,18 +1,21 @@
// TODO: make a choice: mpsc vs flume.
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
use std::sync::{Arc, RwLock}; use std::sync::Arc;
use std::time::Duration; 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::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use tracing::{debug, error, warn};
use matrix_sdk::{ use matrix_sdk::{
config::SyncSettings, config::SyncSettings,
event_handler::Ctx,
room::Room as MatrixRoom, room::Room as MatrixRoom,
ruma::{ ruma::events::{
events::{
key::verification::{ key::verification::{
done::{OriginalSyncKeyVerificationDoneEvent, ToDeviceKeyVerificationDoneEvent}, done::{OriginalSyncKeyVerificationDoneEvent, ToDeviceKeyVerificationDoneEvent},
key::{OriginalSyncKeyVerificationKeyEvent, ToDeviceKeyVerificationKeyEvent}, key::{OriginalSyncKeyVerificationKeyEvent, ToDeviceKeyVerificationKeyEvent},
@@ -31,12 +34,10 @@ use matrix_sdk::{
typing::SyncTypingEvent, typing::SyncTypingEvent,
SyncMessageLikeEvent, SyncStateEvent, SyncMessageLikeEvent, SyncStateEvent,
}, },
OwnedRoomId, OwnedUserId, Client,
},
Client, DisplayName, RoomMemberships,
}; };
use crate::base::{ByIdRooms, ReactiveStore, Room, Store, UserInfo}; use crate::base::Room;
#[derive(Debug)] #[derive(Debug)]
pub enum LoginStyle { pub enum LoginStyle {
@@ -104,53 +105,183 @@ fn oneshot<T>() -> (ClientReply<T>, ClientResponse<T>) {
pub struct Requester { pub struct Requester {
pub client: Arc<Client>, pub client: Arc<Client>,
pub tx: UnboundedSender<WorkerTask>, pub tx: UnboundedSender<WorkerTask>,
pub rooms_receiver: flume::Receiver<Room>,
} }
impl Requester { impl Requester {
pub fn init(&self) { pub fn init(&self) {
println!("Requester.init BEG");
let (reply, response) = oneshot(); let (reply, response) = oneshot();
self.tx.send(WorkerTask::Init(reply)).unwrap(); self.tx.send(WorkerTask::Init(reply)).unwrap();
println!("Requester.init END");
return response.recv(); return response.recv();
} }
pub fn login(&self, style: LoginStyle) -> anyhow::Result<()> { pub fn login(&self, style: LoginStyle) -> anyhow::Result<()> {
println!("Requester.login BEG");
let (reply, response) = oneshot(); let (reply, response) = oneshot();
self.tx.send(WorkerTask::Login(style, reply)).unwrap(); self.tx.send(WorkerTask::Login(style, reply)).unwrap();
println!("Requester.login END");
return response.recv(); return response.recv();
} }
} }
pub struct MatrixClient { pub struct MatrixClient {
initialized: bool, initialized: bool,
client: Arc<Client>, client: Option<Arc<Client>>,
// sync_token: Option<String>,
load_handle: Option<JoinHandle<()>>, load_handle: Option<JoinHandle<()>>,
sync_handle: Option<JoinHandle<()>>, sync_handle: Option<JoinHandle<()>>,
store: ReactiveStore, rooms_sender: Option<flume::Sender<Room>>,
} }
impl MatrixClient { impl MatrixClient {
fn new(store: ReactiveStore, client: Arc<Client>) -> Self { pub fn new(client: Arc<Client>, rooms_sender: flume::Sender<Room>) -> Self {
Self { Self {
client: client,
initialized: false, initialized: false,
// sync_token: None, client: Some(client),
load_handle: None, load_handle: None,
sync_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<RoomNameEventContent>, _room: MatrixRoom) {
debug!("== on_sync_state_event ==");
dbg!(_ev);
}
async fn on_room_topic_event(ev: SyncStateEvent<RoomTopicEventContent>, _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<RoomMemberEventContent>, _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<RoomMessageEventContent>,
_room: MatrixRoom,
_client: Client,
) {
debug!("== on_sync_message_like_room_message_event ==");
dbg!(ev);
}
async fn on_sync_message_like_reaction_event(
ev: SyncMessageLikeEvent<ReactionEventContent>,
_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::<WorkerTask>(); let (tx, rx) = unbounded_channel::<WorkerTask>();
let (rooms_sender, rooms_receiver) = unbounded::<Room>();
let client = Arc::new( let client = Arc::new(
Client::builder() Client::builder()
@@ -160,7 +291,7 @@ impl MatrixClient {
.unwrap(), .unwrap(),
); );
let mut matrix_client = MatrixClient::new(store, client.clone()); let mut matrix_client = MatrixClient::new(client.clone(), rooms_sender);
tokio::spawn({ tokio::spawn({
async move { async move {
@@ -168,209 +299,18 @@ impl MatrixClient {
} }
}); });
Requester { client, tx } Requester {
} client,
tx,
async fn work(&mut self, mut rx: UnboundedReceiver<WorkerTask>) { rooms_receiver,
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();
} }
} }
async fn run(&mut self, task: WorkerTask) { fn init(&mut self) {
println!("Run({:?}", task); let client = self.client.clone().unwrap();
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);
}
}
}
async fn on_sync_typing_event( // let store = self.store.clone();
_ev: SyncTypingEvent, // client.add_event_handler_context(store);
room: MatrixRoom,
_store: Ctx<ReactiveStore>,
) {
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<ReactiveStore>) {
println!("== on_presence_event ==");
dbg!(_ev);
}
async fn on_sync_state_event(
_ev: SyncStateEvent<RoomNameEventContent>,
_room: MatrixRoom,
_store: Ctx<ReactiveStore>,
) {
println!("== on_sync_state_event ==");
dbg!(_ev);
}
async fn on_room_topic_event(
ev: SyncStateEvent<RoomTopicEventContent>,
room: MatrixRoom,
reactive_store: Ctx<ReactiveStore>,
) {
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<RoomMemberEventContent>,
room: MatrixRoom,
store_ctx: Ctx<ReactiveStore>,
) {
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<RoomMessageEventContent>,
_room: MatrixRoom,
_client: Client,
_store: Ctx<ReactiveStore>,
) {
println!("== on_sync_message_like_room_message_event ==");
// dbg!(_ev);
}
async fn on_sync_message_like_reaction_event(
_ev: SyncMessageLikeEvent<ReactionEventContent>,
_room: MatrixRoom,
_store: Ctx<ReactiveStore>,
) {
println!("== on_sync_message_like_reaction_event ==");
}
async fn on_original_sync_room_redaction_event(
_ev: OriginalSyncRoomRedactionEvent,
_room: MatrixRoom,
_store: Ctx<ReactiveStore>,
) {
println!("== on_original_sync_room_redaction_event ==");
}
async fn on_original_sync_room_member_event(
_ev: OriginalSyncRoomMemberEvent,
room: MatrixRoom,
_client: Client,
store_ctx: Ctx<ReactiveStore>,
) {
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<ReactiveStore>,
) {
println!("== on_original_sync_key_verif_start_event ==");
}
async fn on_original_sync_key_verif_key_event(
_ev: OriginalSyncKeyVerificationKeyEvent,
_client: Client,
_store: Ctx<ReactiveStore>,
) {
println!("== on_original_sync_key_verif_key_event ==");
}
async fn on_original_sync_key_verif_done_event(
_ev: OriginalSyncKeyVerificationDoneEvent,
_client: Client,
_store: Ctx<ReactiveStore>,
) {
println!("== on_original_sync_key_verif_done_event ==");
}
async fn on_device_key_verif_req_event(
_ev: ToDeviceKeyVerificationRequestEvent,
_client: Client,
_store: Ctx<ReactiveStore>,
) {
println!("== on_device_key_verif_req_event ==");
}
async fn on_device_key_verif_start_event(
_ev: ToDeviceKeyVerificationStartEvent,
_client: Client,
_store: Ctx<ReactiveStore>,
) {
println!("== on_device_key_verif_start_event ==");
}
async fn on_device_key_verif_key_event(
_ev: ToDeviceKeyVerificationKeyEvent,
_client: Client,
_store: Ctx<ReactiveStore>,
) {
println!("== on_device_key_verif_key_event ==");
}
async fn on_device_key_verif_done_event(
_ev: ToDeviceKeyVerificationDoneEvent,
_client: Client,
_store: Ctx<ReactiveStore>,
) {
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 _ = client.add_event_handler(MatrixClient::on_sync_typing_event); let _ = client.add_event_handler(MatrixClient::on_sync_typing_event);
let _ = client.add_event_handler(MatrixClient::on_presence_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_topic_event);
let _ = client.add_event_handler(MatrixClient::on_room_member_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; self.initialized = true;
} }
async fn load_user_infos(&self) { async fn refresh_rooms(client: &Client, rooms_sender: &Sender<Room>) {
let mut store = self.store.read().unwrap().to_owned(); let joined_matrix_rooms_ref = &client.joined_rooms();
let invited_matrix_rooms_ref = &client.invited_rooms();
let user_id = self.client.user_id().unwrap(); for matrix_rooms in vec![joined_matrix_rooms_ref, invited_matrix_rooms_ref] {
store.user_id = Some(OwnedUserId::from(user_id)); for matrix_room in matrix_rooms.iter() {
let room = Room::new(
let res = self.client.account().get_profile().await.unwrap(); Arc::new(matrix_room.to_owned()),
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<Store>) {
// 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, None,
is_direct, 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() { async fn refresh_rooms_forever(client: &Client, rooms_channel: &Sender<Room>) {
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<Store>) {
// TODO: Add interval to config // TODO: Add interval to config
let mut interval = tokio::time::interval(Duration::from_secs(5)); let mut interval = tokio::time::interval(Duration::from_secs(5));
loop { loop {
Self::refresh_rooms(client, store).await; Self::refresh_rooms(client, rooms_channel).await;
error!("== Interval tick BEG ==");
interval.tick().await; interval.tick().await;
error!("== Interval tick END ==");
} }
} }
async fn login_and_sync(&mut self, style: LoginStyle) -> anyhow::Result<()> { async fn login_and_sync(&mut self, style: LoginStyle) -> anyhow::Result<()> {
let client = self.client.clone(); let client = self.client.clone().unwrap();
match style { match style {
LoginStyle::Password(username, password) => { 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({ self.sync_handle = tokio::spawn({
async move { async move {
// Sync once so we receive the client state and old messages // Sync once so we receive the client state and old messages
let _ = client.sync_once(SyncSettings::default()).await; 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 { loop {
let settings = SyncSettings::default(); let settings = SyncSettings::default();
let _ = client.sync(settings).await; let _ = client.sync(settings).await;
@@ -518,11 +399,61 @@ impl MatrixClient {
}) })
.into(); .into();
let mut store = self.store.read().unwrap().to_owned(); self.start_background_tasks(synchronized_rx);
store.is_logged = true;
let _ = self.store.write(store);
println!("User connected to the homeserver");
Ok(()) Ok(())
} }
fn start_background_tasks(&mut self, synchronized_rx: FlumeReceiver<bool>) {
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<WorkerTask>) {
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);
}
}
}
} }