🏗️ 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
[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"

View File

@@ -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<MatrixRoom>,
pub topic: Option<String>,
pub topic: Option<RefCell<String>>,
pub members: HashMap<OwnedUserId, RoomMember>,
pub is_direct: Option<bool>,
}
@@ -44,7 +41,7 @@ pub struct Room {
impl Room {
pub fn new(
matrix_room: Arc<MatrixRoom>,
topic: Option<String>,
topic: Option<RefCell<String>>,
is_direct: Option<bool>,
) -> Self {
Self {
@@ -58,21 +55,23 @@ impl Room {
pub fn name(&self) -> Option<String> {
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<OwnedRoomId, Arc<RwLock<Room>>>;
pub type ByIdRooms = HashMap<OwnedRoomId, RefCell<Room>>;
pub type ByIdUserInfos = HashMap<OwnedUserId, UserInfo>;
#[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<OwnedUserId>,
}
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<UseRw<Store>>;
#[derive(Clone)]
pub struct AppSettings {
pub requester: Option<Arc<Requester>>,
pub requester: Option<Requester>,
pub store: Store,
}
impl AppSettings {
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 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",

View File

@@ -1,36 +1,46 @@
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<Store>) -> 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<AppSettings>, rooms_atom: UseAtomRef<ByIdRooms>) {
loop {
let requester = &app_settings.read().requester;
let rooms_len = rooms.len();
let mut groups = Vec::<Room>::with_capacity(rooms_len);
let mut directs = Vec::<Room>::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);
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::<Room>::new(room));
} 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: Add offline users ?
@@ -40,8 +50,8 @@ pub fn Contacts<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> 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},
},
})
}

View File

@@ -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<Vec<Room>>) -> Element {
println!("ContactsSection rendering");
pub fn filter_people_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
}
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);
@@ -33,11 +70,19 @@ pub fn ContactsSection<'a>(cx: Scope, name: &'a str, contacts: RefCell<Vec<Room>
]
.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<Vec<Room>
}
);
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<Vec<Room>
style: "color: darkgrey;",
room_topic,
},
},
)
})
});
cx.render(rsx! {

View File

@@ -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<Store>) -> 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<Store>) -> 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<Store>) -> Element {
},
},
Contacts {rw_store: rw_store},
Contacts {},
div {
class: ClassName::FOOTER,

View File

@@ -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 = "<Enter a personal message>";
#[inline_props]
pub fn UserInfos<'a>(cx: Scope, rw_store: &'a UseRw<Store>) -> 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<Store>) -> 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,

View File

@@ -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<Store>) -> Element {
let app_context = use_shared_state::<AppSettings>(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<Store>) -> 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<Store>) -> 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);
}
});
};

View File

@@ -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<Store>) -> 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 {})
}
})
}

View File

@@ -2,29 +2,31 @@
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<AppSettings>) -> 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#"
@@ -48,17 +50,19 @@ fn App(cx: Scope<AppSettings>) -> 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(())
}

View File

@@ -1,18 +1,21 @@
// 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::{
ruma::events::{
key::verification::{
done::{OriginalSyncKeyVerificationDoneEvent, ToDeviceKeyVerificationDoneEvent},
key::{OriginalSyncKeyVerificationKeyEvent, ToDeviceKeyVerificationKeyEvent},
@@ -31,12 +34,10 @@ use matrix_sdk::{
typing::SyncTypingEvent,
SyncMessageLikeEvent, SyncStateEvent,
},
OwnedRoomId, OwnedUserId,
},
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<T>() -> (ClientReply<T>, ClientResponse<T>) {
pub struct Requester {
pub client: Arc<Client>,
pub tx: UnboundedSender<WorkerTask>,
pub rooms_receiver: flume::Receiver<Room>,
}
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<Client>,
// sync_token: Option<String>,
client: Option<Arc<Client>>,
load_handle: Option<JoinHandle<()>>,
sync_handle: Option<JoinHandle<()>>,
store: ReactiveStore,
rooms_sender: Option<flume::Sender<Room>>,
}
impl MatrixClient {
fn new(store: ReactiveStore, client: Arc<Client>) -> Self {
pub fn new(client: Arc<Client>, rooms_sender: flume::Sender<Room>) -> 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<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 (rooms_sender, rooms_receiver) = unbounded::<Room>();
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<WorkerTask>) {
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<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 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<Room>) {
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<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()),
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,
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() {
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>) {
async fn refresh_rooms_forever(client: &Client, rooms_channel: &Sender<Room>) {
// 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<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);
}
}
}
}