[fractal] Sync through matrix-sdk
- From: Alexandre Franke <afranke src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] Sync through matrix-sdk
- Date: Tue, 1 Dec 2020 20:22:01 +0000 (UTC)
commit 8968b9d12e49644216aa9a1940f488070647a6aa
Author: Alejandro Domínguez <adomu net-c com>
Date: Tue Sep 29 00:11:52 2020 +0200
Sync through matrix-sdk
fractal-gtk/Cargo.toml | 2 +-
fractal-gtk/src/appop/invite.rs | 13 +-
fractal-gtk/src/appop/member.rs | 30 +-
fractal-gtk/src/appop/notifications.rs | 2 +-
fractal-gtk/src/appop/room.rs | 22 +-
fractal-gtk/src/appop/sync.rs | 44 +-
fractal-gtk/src/backend/media.rs | 30 +-
fractal-gtk/src/backend/room.rs | 17 +-
fractal-gtk/src/backend/sync.rs | 313 +++++--------
fractal-gtk/src/error.rs | 34 +-
fractal-gtk/src/meson.build | 1 -
fractal-gtk/src/model/event.rs | 18 -
fractal-gtk/src/model/message.rs | 440 +++++++++++++------
fractal-gtk/src/model/mod.rs | 1 -
fractal-gtk/src/model/room.rs | 604 +++++++++++++++-----------
fractal-gtk/src/passwd.rs | 9 +-
fractal-gtk/src/widgets/media_viewer.rs | 2 +-
fractal-gtk/src/widgets/members_list.rs | 10 +-
fractal-gtk/src/widgets/roomlist.rs | 4 +-
fractal-gtk/src/widgets/roomrow.rs | 2 +-
fractal-matrix-api/src/meson.build | 3 -
fractal-matrix-api/src/r0.rs | 2 -
fractal-matrix-api/src/r0/filter.rs | 104 -----
fractal-matrix-api/src/r0/sync.rs | 1 -
fractal-matrix-api/src/r0/sync/sync_events.rs | 226 ----------
25 files changed, 871 insertions(+), 1063 deletions(-)
---
diff --git a/fractal-gtk/Cargo.toml b/fractal-gtk/Cargo.toml
index 713ae9be..b1fb3d8d 100644
--- a/fractal-gtk/Cargo.toml
+++ b/fractal-gtk/Cargo.toml
@@ -81,4 +81,4 @@ features = ["serde_untagged"]
[dependencies.tokio]
version = "0.2.22"
-features = ["rt-threaded"]
+features = ["rt-threaded", "time"]
diff --git a/fractal-gtk/src/appop/invite.rs b/fractal-gtk/src/appop/invite.rs
index 5563bea5..40b9bdfa 100644
--- a/fractal-gtk/src/appop/invite.rs
+++ b/fractal-gtk/src/appop/invite.rs
@@ -257,19 +257,18 @@ impl AppOp {
let empty = String::new();
let room_name = room_name.unwrap_or(&empty);
let title = i18n_k("Join {room_name}?", &[("room_name", &room_name)]);
- let secondary;
- if let Some(ref sender) = sender {
+ let secondary = if let Some(ref sender) = sender {
let sender_name = sender.get_alias();
- secondary = i18n_k(
+ i18n_k(
"You’ve been invited to join <b>{room_name}</b> room by <b>{sender_name}</b>",
&[("room_name", &room_name), ("sender_name", &sender_name)],
- );
+ )
} else {
- secondary = i18n_k(
+ i18n_k(
"You’ve been invited to join <b>{room_name}</b>",
&[("room_name", &room_name)],
- );
- }
+ )
+ };
dialog.set_property_text(Some(title.as_str()));
dialog.set_property_secondary_use_markup(true);
diff --git a/fractal-gtk/src/appop/member.rs b/fractal-gtk/src/appop/member.rs
index b7e85676..094be415 100644
--- a/fractal-gtk/src/appop/member.rs
+++ b/fractal-gtk/src/appop/member.rs
@@ -1,6 +1,10 @@
use crate::backend::{user, HandleError};
use either::Either;
use fractal_api::{
+ events::{
+ room::member::{MemberEventContent, MembershipState},
+ StateEvent,
+ },
identifiers::{RoomId, UserId},
url::Url,
};
@@ -16,7 +20,7 @@ use crate::appop::AppOp;
use crate::widgets;
use crate::App;
-use crate::model::{event::Event, member::Member};
+use crate::model::member::Member;
#[derive(Debug, Clone)]
pub enum SearchType {
@@ -25,7 +29,7 @@ pub enum SearchType {
}
impl AppOp {
- pub fn member_level(&self, member: &Member) -> i32 {
+ pub fn member_level(&self, member: &Member) -> i64 {
self.active_room
.as_ref()
.and_then(|a_room| self.rooms.get(a_room)?.admins.get(&member.uid))
@@ -49,28 +53,28 @@ impl AppOp {
}
}
- pub fn room_member_event(&mut self, ev: Event) {
+ pub fn room_member_event(&mut self, ev: StateEvent<MemberEventContent>) {
// NOTE: maybe we should show this events in the message list to notify enters and leaves
// to the user
let sender = ev.sender;
- match ev.content["membership"].as_str() {
- Some("leave") => {
- if let Some(r) = self.rooms.get_mut(&ev.room) {
+ match ev.content.membership {
+ MembershipState::Leave => {
+ if let Some(r) = self.rooms.get_mut(&ev.room_id) {
r.members.remove(&sender);
}
}
- Some("join") => {
+ MembershipState::Join => {
let m = Member {
- avatar: ev.content["avatar_url"]
- .as_str()
- .map(Url::parse)
- .and_then(Result::ok)
+ avatar: ev
+ .content
+ .avatar_url
+ .and_then(|u| Url::parse(&u).ok())
.map(Either::Left),
- alias: ev.content["displayname"].as_str().map(String::from),
+ alias: ev.content.displayname,
uid: sender,
};
- if let Some(r) = self.rooms.get_mut(&ev.room.clone()) {
+ if let Some(r) = self.rooms.get_mut(&ev.room_id) {
r.members.insert(m.uid.clone(), m);
}
}
diff --git a/fractal-gtk/src/appop/notifications.rs b/fractal-gtk/src/appop/notifications.rs
index d39ce3e9..85f63c24 100644
--- a/fractal-gtk/src/appop/notifications.rs
+++ b/fractal-gtk/src/appop/notifications.rs
@@ -8,7 +8,7 @@ impl AppOp {
self.update_title();
}
- pub fn set_room_notifications(&mut self, room_id: RoomId, n: i32, h: i32) {
+ pub fn set_room_notifications(&mut self, room_id: RoomId, n: u64, h: u64) {
if let Some(r) = self.rooms.get_mut(&room_id) {
r.notifications = n;
r.highlight = h;
diff --git a/fractal-gtk/src/appop/room.rs b/fractal-gtk/src/appop/room.rs
index 72907110..021b77ed 100644
--- a/fractal-gtk/src/appop/room.rs
+++ b/fractal-gtk/src/appop/room.rs
@@ -53,10 +53,11 @@ impl AppOp {
for room in rooms {
// removing left rooms
if let RoomMembership::Left(kicked) = room.membership.clone() {
- if let Reason::Kicked(reason, kicker) = kicked {
+ if let Reason::Kicked(reason, kicker_uid) = kicked {
if let Some(r) = self.rooms.get(&room.id) {
let room_name = r.name.clone().unwrap_or_default();
- self.kicked_room(room_name, reason, kicker.alias.unwrap_or_default());
+ let kicker = r.members.get(&kicker_uid);
+ self.kicked_room(room_name, reason, kicker);
}
}
if self.active_room.as_ref().map_or(false, |x| x == &room.id) {
@@ -182,8 +183,9 @@ impl AppOp {
if let Some(language) = room.language.clone() {
self.set_language(language);
}
- if let RoomMembership::Invited(ref sender) = room.membership {
- self.show_inv_dialog(Some(sender), room.name.as_ref());
+ if let RoomMembership::Invited(ref sender_uid) = room.membership {
+ let sender = room.members.get(sender_uid);
+ self.show_inv_dialog(sender, room.name.as_ref());
self.invitation_roomid = Some(room.id.clone());
return;
}
@@ -359,7 +361,7 @@ impl AppOp {
dialog.present();
}
- pub fn kicked_room(&self, room_name: String, reason: String, kicker: String) {
+ pub fn kicked_room(&self, room_name: String, reason: String, kicker: Option<&Member>) {
let parent: gtk::Window = self
.ui
.builder
@@ -367,7 +369,15 @@ impl AppOp {
.expect("Can't find main_window in ui file.");
let viewer = widgets::KickedDialog::new();
viewer.set_parent_window(&parent);
- viewer.show(&room_name, &reason, &kicker);
+ let kicker_str = kicker
+ .map(|k| {
+ k.alias
+ .as_ref()
+ .map(String::as_str)
+ .unwrap_or(k.uid.as_str())
+ })
+ .unwrap_or_default();
+ viewer.show(&room_name, &reason, kicker_str);
}
pub fn create_new_room(&mut self) {
diff --git a/fractal-gtk/src/appop/sync.rs b/fractal-gtk/src/appop/sync.rs
index e9a63e31..0400bc28 100644
--- a/fractal-gtk/src/appop/sync.rs
+++ b/fractal-gtk/src/appop/sync.rs
@@ -1,9 +1,6 @@
-use log::info;
-use std::thread;
-
use crate::util::i18n::i18n;
-use crate::app::App;
+use crate::app::{App, RUNTIME};
use crate::appop::AppOp;
use crate::backend::{
sync::{self, RoomElement, SyncRet},
@@ -19,7 +16,7 @@ impl AppOp {
}
}
- pub fn sync(&mut self, initial: bool, number_tries: u64) {
+ pub fn sync(&mut self, initial: bool, number_tries: u32) {
if let (Some(login_data), false) = (self.login_data.clone(), self.syncing) {
self.syncing = true;
// for the initial sync we set the since to None to avoid long syncing
@@ -28,16 +25,18 @@ impl AppOp {
// https://matrix.org/docs/spec/client_server/latest.html#syncing
let join_to_room = self.join_to_room.clone();
let since = self.since.clone().filter(|_| !initial);
- thread::spawn(move || {
- match sync::sync(
- login_data.session_client.clone(),
- login_data.access_token,
+ RUNTIME.spawn(async move {
+ let query = sync::sync(
+ login_data.session_client,
login_data.uid,
join_to_room,
since,
initial,
number_tries,
- ) {
+ )
+ .await;
+
+ match query {
Ok(SyncRet::NoSince { rooms, next_batch }) => {
match rooms {
Ok((rooms, default)) => {
@@ -56,13 +55,11 @@ impl AppOp {
}
};
- info!("SYNC");
let s = Some(next_batch);
APPOP!(synced, (s));
}
Ok(SyncRet::WithSince {
update_rooms,
- room_messages,
room_notifications,
update_rooms_2,
other,
@@ -71,15 +68,9 @@ impl AppOp {
match update_rooms {
Ok(rooms) => {
let clear_room_list = false;
+ let msgs: Vec<_> =
+ rooms.iter().flat_map(|r| &r.messages).cloned().collect();
APPOP!(set_rooms, (rooms, clear_room_list));
- }
- Err(err) => {
- err.handle_error();
- }
- }
-
- match room_messages {
- Ok(msgs) => {
APPOP!(show_room_messages, (msgs));
}
Err(err) => {
@@ -99,8 +90,14 @@ impl AppOp {
for (room_id, unread_notifications) in room_notifications {
let r = room_id;
- let n = unread_notifications.notification_count;
- let h = unread_notifications.highlight_count;
+ let n: u64 = unread_notifications
+ .notification_count
+ .map(Into::into)
+ .unwrap_or_default();
+ let h: u64 = unread_notifications
+ .highlight_count
+ .map(Into::into)
+ .unwrap_or_default();
APPOP!(set_room_notifications, (r, n, h));
}
@@ -133,7 +130,6 @@ impl AppOp {
}
}
- info!("SYNC");
let s = Some(next_batch);
APPOP!(synced, (s));
}
@@ -152,7 +148,7 @@ impl AppOp {
self.initial_sync(false);
}
- pub fn sync_error(&mut self, number_tries: u64) {
+ pub fn sync_error(&mut self, number_tries: u32) {
self.syncing = false;
self.sync(false, number_tries);
}
diff --git a/fractal-gtk/src/backend/media.rs b/fractal-gtk/src/backend/media.rs
index 3176624d..b8db5019 100644
--- a/fractal-gtk/src/backend/media.rs
+++ b/fractal-gtk/src/backend/media.rs
@@ -1,8 +1,9 @@
use super::MediaError;
use crate::globals;
-use fractal_api::identifiers::{Error as IdError, EventId, RoomId};
+use fractal_api::identifiers::{EventId, RoomId};
use fractal_api::url::Url;
use fractal_api::{Client as MatrixClient, Error as MatrixError};
+use std::convert::TryInto;
use std::path::PathBuf;
use crate::model::message::Message;
@@ -48,14 +49,11 @@ pub async fn get_media_list(
.ok()
}
-enum GetRoomMediaListError {
- Matrix(MatrixError),
- EventsDeserialization(IdError),
-}
+struct GetRoomMediaListError(MatrixError);
-impl From<MatrixError> for GetRoomMediaListError {
- fn from(err: MatrixError) -> Self {
- Self::Matrix(err)
+impl<T: Into<MatrixError>> From<T> for GetRoomMediaListError {
+ fn from(err: T) -> Self {
+ Self(err.into())
}
}
@@ -79,14 +77,18 @@ async fn get_room_media_list(
let response = session_client.room_messages(request).await?;
let prev_batch = response.end.unwrap_or_default();
- // Deserialization to JsonValue should not fail
- let evs = response
+
+ let media_list = response
.chunk
- .iter()
+ .into_iter()
.rev()
- .map(|ev| serde_json::to_value(ev.json().get()).unwrap());
- let media_list = Message::from_json_events(room_id, evs)
- .map_err(GetRoomMediaListError::EventsDeserialization)?;
+ .filter_map(|ev| {
+ ev.deserialize()
+ .map(TryInto::try_into)
+ .map(Result::ok)
+ .transpose()
+ })
+ .collect::<Result<_, _>>()?;
Ok((media_list, prev_batch))
}
diff --git a/fractal-gtk/src/backend/room.rs b/fractal-gtk/src/backend/room.rs
index a9423054..a2064d47 100644
--- a/fractal-gtk/src/backend/room.rs
+++ b/fractal-gtk/src/backend/room.rs
@@ -3,7 +3,7 @@ use serde_json::json;
use fractal_api::{
api::error::ErrorKind as RumaErrorKind,
- identifiers::{Error as IdError, EventId, RoomId, RoomIdOrAliasId, UserId},
+ identifiers::{EventId, RoomId, RoomIdOrAliasId, UserId},
url::{ParseError as UrlError, Url},
Client as MatrixClient, Error as MatrixError, FromHttpResponseError as RumaResponseError,
ServerError,
@@ -12,7 +12,7 @@ use serde::Serialize;
use std::io::Error as IoError;
use std::path::Path;
-use std::convert::TryFrom;
+use std::convert::{TryFrom, TryInto};
use std::time::Duration;
use crate::globals;
@@ -228,7 +228,6 @@ pub async fn get_room_members(
pub enum RoomMessagesToError {
MessageNotSent,
Matrix(MatrixError),
- EventsDeserialization(IdError),
}
impl<T: Into<MatrixError>> From<T> for RoomMessagesToError {
@@ -260,13 +259,17 @@ pub async fn get_room_messages(
let response = session_client.room_messages(request).await?;
let prev_batch = response.end;
- let evs = response
+ let list: Vec<Message> = response
.chunk
.into_iter()
.rev()
- .map(|ev| serde_json::to_value(ev.json().get()).unwrap());
- let list = Message::from_json_events(&room_id, evs)
- .map_err(RoomMessagesToError::EventsDeserialization)?;
+ .filter_map(|ev| {
+ ev.deserialize()
+ .map(TryInto::try_into)
+ .map(Result::ok)
+ .transpose()
+ })
+ .collect::<Result<_, _>>()?;
Ok((list, room_id, prev_batch))
}
diff --git a/fractal-gtk/src/backend/sync.rs b/fractal-gtk/src/backend/sync.rs
index 3aef92ba..f46f214c 100644
--- a/fractal-gtk/src/backend/sync.rs
+++ b/fractal-gtk/src/backend/sync.rs
@@ -1,36 +1,31 @@
-use crate::client::ProxySettings;
-use crate::error::{Error, StandardErrorResponse};
use crate::globals;
use crate::model::{
- event::Event,
member::Member,
- message::Message,
room::{Room, RoomMembership, RoomTag},
};
-use fractal_api::r0::filter::EventFilter;
-use fractal_api::r0::filter::Filter;
-use fractal_api::r0::filter::RoomEventFilter;
-use fractal_api::r0::filter::RoomFilter;
-use fractal_api::r0::sync::sync_events::request as sync_events;
-use fractal_api::r0::sync::sync_events::IncludeState;
-use fractal_api::r0::sync::sync_events::Parameters as SyncParameters;
-use fractal_api::r0::sync::sync_events::Response as SyncResponse;
-use fractal_api::r0::sync::sync_events::UnreadNotificationsCount;
-use fractal_api::r0::AccessToken;
+use fractal_api::api::r0::filter::Filter as EventFilter;
+use fractal_api::api::r0::filter::FilterDefinition;
+use fractal_api::api::r0::filter::LazyLoadOptions;
+use fractal_api::api::r0::filter::RoomEventFilter;
+use fractal_api::api::r0::filter::RoomFilter;
+use fractal_api::api::r0::sync::sync_events::Filter;
+use fractal_api::api::r0::sync::sync_events::UnreadNotificationsCount;
+use fractal_api::assign;
+use fractal_api::events::room::member::MemberEventContent;
+use fractal_api::events::AnyEphemeralRoomEventContent;
+use fractal_api::events::AnySyncMessageEvent;
+use fractal_api::events::AnySyncRoomEvent;
+use fractal_api::events::AnySyncStateEvent;
+use fractal_api::events::StateEvent;
+use fractal_api::SyncSettings;
use fractal_api::identifiers::{EventId, RoomId, UserId};
-use fractal_api::reqwest::blocking::{Client, Response};
use fractal_api::Client as MatrixClient;
+use fractal_api::Error as MatrixError;
use log::error;
-use serde::de::DeserializeOwned;
-use serde_json::value::from_value;
-use std::{
- collections::HashMap,
- convert::{TryFrom, TryInto},
- thread, time,
-};
+use std::{collections::HashMap, time::Duration};
-use super::{remove_matrix_access_token_if_present, HandleError};
+use super::{get_ruma_client_error, remove_matrix_access_token_if_present, HandleError};
use crate::app::App;
use crate::APPOP;
@@ -38,12 +33,12 @@ pub enum RoomElement {
Name(RoomId, String),
Topic(RoomId, String),
NewAvatar(RoomId),
- MemberEvent(Event),
+ MemberEvent(StateEvent<MemberEventContent>),
RemoveMessage(RoomId, EventId),
}
#[derive(Debug)]
-pub struct SyncError(Error, u64);
+pub struct SyncError(MatrixError, u32);
impl HandleError for SyncError {
fn handle_error(&self) {
@@ -58,9 +53,9 @@ impl HandleError for SyncError {
}
#[derive(Debug)]
-pub struct RoomsError(Error);
+pub struct RoomsError(MatrixError);
-impl<T: Into<Error>> From<T> for RoomsError {
+impl<T: Into<MatrixError>> From<T> for RoomsError {
fn from(err: T) -> Self {
Self(err.into())
}
@@ -69,9 +64,9 @@ impl<T: Into<Error>> From<T> for RoomsError {
impl HandleError for RoomsError {}
#[derive(Debug)]
-pub struct UpdateRoomsError(Error);
+pub struct UpdateRoomsError(MatrixError);
-impl<T: Into<Error>> From<T> for UpdateRoomsError {
+impl<T: Into<MatrixError>> From<T> for UpdateRoomsError {
fn from(err: T) -> Self {
Self(err.into())
}
@@ -80,20 +75,9 @@ impl<T: Into<Error>> From<T> for UpdateRoomsError {
impl HandleError for UpdateRoomsError {}
#[derive(Debug)]
-pub struct RoomMessagesError(Error);
-
-impl<T: Into<Error>> From<T> for RoomMessagesError {
- fn from(err: T) -> Self {
- Self(err.into())
- }
-}
-
-impl HandleError for RoomMessagesError {}
-
-#[derive(Debug)]
-pub struct RoomElementError(Error);
+pub struct RoomElementError(MatrixError);
-impl<T: Into<Error>> From<T> for RoomElementError {
+impl<T: Into<MatrixError>> From<T> for RoomElementError {
fn from(err: T) -> Self {
Self(err.into())
}
@@ -108,7 +92,6 @@ pub enum SyncRet {
},
WithSince {
update_rooms: Result<Vec<Room>, UpdateRoomsError>,
- room_messages: Result<Vec<Message>, RoomMessagesError>,
room_notifications: HashMap<RoomId, UnreadNotificationsCount>,
update_rooms_2: Result<Vec<Room>, UpdateRoomsError>,
other: Result<Vec<RoomElement>, RoomElementError>,
@@ -116,83 +99,56 @@ pub enum SyncRet {
},
}
-pub fn sync(
+pub async fn sync(
session_client: MatrixClient,
- access_token: AccessToken,
user_id: UserId,
join_to_room: Option<RoomId>,
since: Option<String>,
initial: bool,
- number_tries: u64,
+ number_tries: u32,
) -> Result<SyncRet, SyncError> {
- let base = session_client.homeserver().clone();
-
- let (timeout, filter) = if !initial {
- (time::Duration::from_secs(30), Default::default())
+ let timeline_not_types = [String::from("m.call.*")];
+ let timeline_types = [String::from("m.room.message"), String::from("m.sticker")];
+ let state_types = [String::from("m.room.*")];
+ let sync_settings = if !initial {
+ SyncSettings::new().timeout(Duration::from_secs(30))
} else {
- let filter = Filter {
- room: Some(RoomFilter {
- state: Some(RoomEventFilter {
- lazy_load_members: true,
- types: Some(vec!["m.room.*"]),
- ..Default::default()
+ // Don't filter event fields, it breaks deserialization.
+ // Clearly the Matrix API is very static-typing-unfriendly right now.
+ let filter = assign!(FilterDefinition::empty(), {
+ presence: assign!(EventFilter::empty(), {
+ types: Some(&[]),
+ }),
+ room: assign!(RoomFilter::empty(), {
+ timeline: assign!(RoomEventFilter::empty(), {
+ not_types: &timeline_not_types,
+ limit: Some(globals::PAGE_LIMIT.into()),
+ types: Some(&timeline_types),
}),
- timeline: Some(RoomEventFilter {
- types: Some(vec!["m.room.message", "m.sticker"]),
- not_types: vec!["m.call.*"],
- limit: Some(globals::PAGE_LIMIT as i32),
- ..Default::default()
+ ephemeral: assign!(RoomEventFilter::empty(), {
+ types: Some(&[]),
}),
- ephemeral: Some(RoomEventFilter {
- types: Some(vec![]),
- ..Default::default()
+ state: assign!(RoomEventFilter::empty(), {
+ types: Some(&state_types),
+ lazy_load_options: LazyLoadOptions::Enabled {
+ include_redundant_members: false,
+ },
}),
- ..Default::default()
}),
- presence: Some(EventFilter {
- types: Some(vec![]),
- ..Default::default()
- }),
- event_fields: Some(vec![
- "type",
- "content",
- "sender",
- "origin_server_ts",
- "event_id",
- "unsigned",
- ]),
- ..Default::default()
- };
+ });
- (Default::default(), filter)
+ SyncSettings::new().filter(Filter::FilterDefinition(filter))
};
- let params = SyncParameters {
- access_token: access_token.clone(),
- filter,
- include_state: IncludeState::Changed {
- since: since.clone().unwrap_or_default(),
- timeout,
- },
- set_presence: Default::default(),
+ let sync_settings = match since.clone() {
+ Some(sync_token) => sync_settings.token(sync_token),
+ None => sync_settings,
};
- let client_builder_timeout = Client::builder().timeout(Some(globals::TIMEOUT + timeout));
-
- let query = ProxySettings::current().and_then(|proxy_settings| {
- let client = proxy_settings
- .apply_to_blocking_client_builder(client_builder_timeout)
- .build()?;
- let request = sync_events(base.clone(), ¶ms)?;
- let response = client.execute(request)?;
-
- matrix_response::<SyncResponse>(response)
- });
-
- match query {
+ match session_client.sync_once(sync_settings).await {
Ok(response) => {
if since.is_none() {
- let rooms = Room::from_sync_response(session_client, &response, user_id)
+ let rooms = Room::from_sync_response(&response, user_id)
.map(|rooms| {
let def = join_to_room
.and_then(|jtr| rooms.iter().find(|x| x.id == jtr).cloned());
@@ -208,20 +164,7 @@ pub fn sync(
// New rooms
let update_rooms =
- Room::from_sync_response(session_client, &response, user_id.clone())
- .map_err(Into::into);
-
- // Message events
- let room_messages = join
- .iter()
- .try_fold(Vec::new(), |mut acum, (k, room)| {
- let events = room.timeline.events.clone();
- Message::from_json_events(&k, events).map(|msgs| {
- acum.extend(msgs);
- acum
- })
- })
- .map_err(Into::into);
+ Room::from_sync_response(&response, user_id.clone()).map_err(Into::into);
// Room notifications
let room_notifications = join
@@ -230,96 +173,76 @@ pub fn sync(
.collect();
// Typing notifications
- let update_rooms_2 = Ok(join
+ let update_rooms_2 = join
.iter()
.map(|(k, room)| {
- let ephemerals = &room.ephemeral.events;
- let typing: Vec<Member> = ephemerals.iter()
- .flat_map(|event| {
- event
- .get("content")
- .and_then(|x| x.get("user_ids"))
- .and_then(|x| x.as_array())
- .unwrap_or(&vec![])
- .to_owned()
+ let typing: Vec<Member> = room.ephemeral.events
+ .iter()
+ .map(|ev| ev.deserialize())
+ .collect::<Result<Vec<_>, _>>()?
+ .into_iter()
+ .filter_map(|event| match event.content() {
+ AnyEphemeralRoomEventContent::Typing(content) => {
+ Some(content.user_ids)
+ }
+ _ => None,
})
- .filter_map(|user| from_value(user).ok())
+ .flatten()
// ignoring the user typing notifications
.filter(|user| *user != user_id)
- .map(|uid| {
- Member {
- uid,
- alias: None,
- avatar: None,
- }
+ .map(|uid| Member {
+ uid,
+ alias: None,
+ avatar: None,
})
.collect();
- Room {
+ Ok(Room {
typing_users: typing,
..Room::new(k.clone(), RoomMembership::Joined(RoomTag::None))
- }
+ })
})
- .collect());
+ .collect();
// Other events
let other = join
.iter()
- .flat_map(|(k, room)| {
+ .flat_map(|(room_id, room)| {
+ let room_id = room_id.clone();
room.timeline
.events
.iter()
- .filter(|x| x["type"] != "m.room.message")
- .map(move |ev| {
- Ok(Event {
- room: k.clone(),
- sender: UserId::try_from(
- ev["sender"].as_str().unwrap_or_default(),
- )?,
- content: ev["content"].clone(),
- redacts: ev["redacts"]
- .as_str()
- .map(|r| r.try_into())
- .transpose()?,
- stype: ev["type"].as_str().map(Into::into).unwrap_or_default(),
- id: ev["id"].as_str().map(Into::into).unwrap_or_default(),
- })
- })
+ .map(move |ev| Ok((room_id.clone(), ev.deserialize()?)))
})
- .filter_map(|ev| {
- let ev = match ev {
- Ok(ev) => ev,
+ .filter_map(|rid_ev| {
+ let (room_id, event) = match rid_ev {
+ Ok(roomid_event) => roomid_event,
Err(err) => return Some(Err(err)),
};
- match ev.stype.as_ref() {
- "m.room.name" => {
- let name = ev.content["name"]
- .as_str()
- .map(Into::into)
- .unwrap_or_default();
- Some(Ok(RoomElement::Name(ev.room.clone(), name)))
+ match event {
+ AnySyncRoomEvent::State(AnySyncStateEvent::RoomName(ev)) => {
+ let name = ev.content.name().map(Into::into).unwrap_or_default();
+ Some(Ok(RoomElement::Name(room_id, name)))
+ }
+ AnySyncRoomEvent::State(AnySyncStateEvent::RoomTopic(ev)) => {
+ Some(Ok(RoomElement::Topic(room_id, ev.content.topic)))
+ }
+ AnySyncRoomEvent::State(AnySyncStateEvent::RoomAvatar(_)) => {
+ Some(Ok(RoomElement::NewAvatar(room_id)))
+ }
+ AnySyncRoomEvent::State(AnySyncStateEvent::RoomMember(ev)) => {
+ Some(Ok(RoomElement::MemberEvent(ev.into_full_event(room_id))))
}
- "m.room.topic" => {
- let t = ev.content["topic"]
- .as_str()
- .map(Into::into)
- .unwrap_or_default();
- Some(Ok(RoomElement::Topic(ev.room.clone(), t)))
+ AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomRedaction(ev)) => {
+ Some(Ok(RoomElement::RemoveMessage(room_id, ev.redacts)))
}
- "m.room.avatar" => Some(Ok(RoomElement::NewAvatar(ev.room.clone()))),
- "m.room.member" => Some(Ok(RoomElement::MemberEvent(ev))),
- "m.room.redaction" => Some(Ok(RoomElement::RemoveMessage(
- ev.room.clone(),
- ev.redacts.expect(
- "Events of type m.room.redaction should have a 'redacts' field",
- ),
- ))),
- "m.sticker" => {
+ AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(_)) => None,
+ AnySyncRoomEvent::Message(AnySyncMessageEvent::Sticker(_)) => {
// This event is managed in the room list
None
}
- _ => {
+ ev => {
error!("EVENT NOT MANAGED: {:?}", ev);
None
}
@@ -331,7 +254,6 @@ pub fn sync(
Ok(SyncRet::WithSince {
update_rooms,
- room_messages,
room_notifications,
update_rooms_2,
other,
@@ -342,36 +264,19 @@ pub fn sync(
Err(err) => {
// we wait if there's an error to avoid 100% CPU
// we wait even longer, if it's a 429 (Too Many Requests) error
- let waiting_time = match err {
- Error::NetworkError(status) if status.as_u16() == 429 => {
- 10 * 2_u64.pow(
- number_tries
- .try_into()
- .expect("The number of sync tries couldn't be transformed into a u32."),
- )
+ let waiting_time = Duration::from_secs(match get_ruma_client_error(&err) {
+ Some(ruma_err) if ruma_err.status_code.as_u16() == 429 => {
+ 10 * 2_u64.pow(number_tries)
}
_ => 10,
- };
+ });
error!(
- "Sync Error, waiting {:?} seconds to respond for the next sync",
- waiting_time
+ "Sync Error, waiting {} seconds to respond for the next sync",
+ waiting_time.as_secs()
);
- thread::sleep(time::Duration::from_secs(waiting_time));
+ tokio::time::delay_for(waiting_time).await;
Err(SyncError(err, number_tries))
}
}
}
-
-/// Returns the deserialized response to the given request. Handles Matrix errors.
-fn matrix_response<T: DeserializeOwned>(response: Response) -> Result<T, Error> {
- if !response.status().is_success() {
- let status = response.status();
- return match response.json::<StandardErrorResponse>() {
- Ok(error_response) => Err(Error::from(error_response)),
- Err(_) => Err(Error::NetworkError(status)),
- };
- }
-
- response.json::<T>().map_err(Into::into)
-}
diff --git a/fractal-gtk/src/error.rs b/fractal-gtk/src/error.rs
index e9034489..43fcbb64 100644
--- a/fractal-gtk/src/error.rs
+++ b/fractal-gtk/src/error.rs
@@ -1,30 +1,7 @@
-use serde::Deserialize;
-
-#[derive(Clone, Debug, Deserialize)]
-pub struct StandardErrorResponse {
- pub errcode: String,
- pub error: String,
-}
-
-type MatrixErrorCode = String;
-
-#[macro_export]
-macro_rules! derror {
- ($from: path, $to: path) => {
- impl From<$from> for Error {
- fn from(_: $from) -> Error {
- $to
- }
- }
- };
-}
-
#[derive(Debug)]
pub enum Error {
- BackendError,
+ GlibError(glib::Error),
ReqwestError(fractal_api::reqwest::Error),
- NetworkError(fractal_api::reqwest::StatusCode),
- MatrixError(MatrixErrorCode, String),
}
impl From<fractal_api::reqwest::Error> for Error {
@@ -33,11 +10,8 @@ impl From<fractal_api::reqwest::Error> for Error {
}
}
-impl From<StandardErrorResponse> for Error {
- fn from(resp: StandardErrorResponse) -> Error {
- Error::MatrixError(resp.errcode, resp.error)
+impl From<glib::Error> for Error {
+ fn from(err: glib::Error) -> Error {
+ Error::GlibError(err)
}
}
-
-derror!(glib::error::Error, Error::BackendError);
-derror!(fractal_api::identifiers::Error, Error::BackendError);
diff --git a/fractal-gtk/src/meson.build b/fractal-gtk/src/meson.build
index e86770af..92fc9f02 100644
--- a/fractal-gtk/src/meson.build
+++ b/fractal-gtk/src/meson.build
@@ -81,7 +81,6 @@ app_sources = files(
'backend/user.rs',
'cache/mod.rs',
'cache/state.rs',
- 'model/event.rs',
'model/fileinfo.rs',
'model/member.rs',
'model/message.rs',
diff --git a/fractal-gtk/src/model/message.rs b/fractal-gtk/src/model/message.rs
index 783a934e..3aeee80d 100644
--- a/fractal-gtk/src/model/message.rs
+++ b/fractal-gtk/src/model/message.rs
@@ -1,15 +1,20 @@
use chrono::prelude::*;
use chrono::DateTime;
-use chrono::TimeZone;
use fractal_api::{
- identifiers::{Error as IdError, EventId, RoomId, UserId},
+ events::{
+ room::message::{MessageEventContent, RedactedMessageEventContent, Relation},
+ sticker::{RedactedStickerEventContent, StickerEventContent},
+ AnyMessageEvent, AnyRedactedMessageEvent, AnyRedactedSyncMessageEvent, AnyRoomEvent,
+ AnySyncMessageEvent, AnySyncRoomEvent, EventContent, MessageEvent, RedactedMessageEvent,
+ },
+ identifiers::{EventId, RoomId, UserId},
url::Url,
};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::cmp::Ordering;
use std::collections::HashMap;
-use std::convert::TryInto;
+use std::convert::TryFrom;
use std::path::PathBuf;
//FIXME make properties private
@@ -61,87 +66,183 @@ impl PartialOrd for Message {
}
}
-impl Message {
- /// List all supported types. By default a message map a m.room.message event, but there's
- /// other events that we want to show in the message history so we map other event types to our
- /// Message struct, like stickers
- const SUPPORTED_EVENTS: [&'static str; 2] = ["m.room.message", "m.sticker"];
+impl From<MessageEvent<MessageEventContent>> for Message {
+ fn from(msg: MessageEvent<MessageEventContent>) -> Self {
+ let source = serde_json::to_string_pretty(&msg).ok();
- pub fn new(
- room: RoomId,
- sender: UserId,
- body: String,
- mtype: String,
- id: Option<EventId>,
- ) -> Self {
- let date = Local::now();
- Message {
- id,
- sender,
- mtype,
- body,
- date,
- room,
- thumb: None,
- local_path_thumb: None,
+ let initial_message = Self {
+ sender: msg.sender,
+ date: msg.origin_server_ts.into(),
+ room: msg.room_id,
+ // It is mandatory for a message event to have
+ // an event_id field
+ id: Some(msg.event_id),
+ mtype: String::new(),
+ body: String::new(),
url: None,
local_path: None,
+ thumb: None,
+ local_path_thumb: None,
formatted_body: None,
format: None,
- source: None,
+ source,
receipt: HashMap::new(),
redacted: false,
in_reply_to: None,
replace: None,
extra_content: None,
- }
- }
-
- /// Generates an unique transaction id for this message
- /// The txn_id is generated using the md5sum of a concatenation of the message room id, the
- /// message body and the date.
- ///
- ///
https://matrix.org/docs/spec/client_server/r0.3.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
- // TODO: Return matrix_sdk::uuid::Uuid
- pub fn get_txn_id(&self) -> String {
- let msg_str = format!("{}{}{}", self.room, self.body, self.date);
- let digest = md5::compute(msg_str.as_bytes());
- format!("{:x}", digest)
- }
+ };
- /// Helper function to use in iterator filter of a matrix.org json response to filter supported
- /// events
- fn supported_event(ev: &JsonValue) -> bool {
- let type_ = ev["type"].as_str().unwrap_or_default();
+ match msg.content {
+ MessageEventContent::Audio(content) => Self {
+ mtype: String::from("m.audio"),
+ body: content.body,
+ url: content.url.and_then(|u| Url::parse(&u).ok()),
+ ..initial_message
+ },
+ MessageEventContent::File(content) => {
+ let url = content.url.and_then(|u| Url::parse(&u).ok());
+ Self {
+ mtype: String::from("m.file"),
+ body: content.body,
+ url: url.clone(),
+ thumb: content
+ .info
+ .and_then(|c_info| Url::parse(&c_info.thumbnail_url?).ok())
+ .or(url),
+ ..initial_message
+ }
+ }
+ MessageEventContent::Image(content) => {
+ let url = content.url.and_then(|u| Url::parse(&u).ok());
+ Self {
+ mtype: String::from("m.image"),
+ body: content.body,
+ url: url.clone(),
+ thumb: content
+ .info
+ .and_then(|c_info| Url::parse(&c_info.thumbnail_url?).ok())
+ .or(url),
+ ..initial_message
+ }
+ }
+ MessageEventContent::Video(content) => {
+ let url = content.url.and_then(|u| Url::parse(&u).ok());
+ Self {
+ mtype: String::from("m.video"),
+ body: content.body,
+ url: url.clone(),
+ thumb: content
+ .info
+ .and_then(|c_info| Url::parse(&c_info.thumbnail_url?).ok())
+ .or(url),
+ ..initial_message
+ }
+ }
+ MessageEventContent::Text(content) => {
+ let (in_reply_to, replace) =
+ content.relates_to.map_or(Default::default(), |r| match r {
+ Relation::Replacement(rep) => (None, Some(rep.event_id)),
+ Relation::Reply { in_reply_to } => (Some(in_reply_to.event_id), None),
+ _ => (None, None),
+ });
+ let (body, formatted, in_reply_to) = content.new_content.map_or(
+ (content.body, content.formatted, in_reply_to),
+ |nc| {
+ let in_reply_to = nc.relates_to.and_then(|r| match r {
+ Relation::Reply { in_reply_to } => Some(in_reply_to.event_id),
+ _ => None,
+ });
- Self::SUPPORTED_EVENTS.contains(&type_)
- }
+ (nc.body, nc.formatted, in_reply_to)
+ },
+ );
+ let (formatted_body, format) = formatted.map_or(Default::default(), |f| {
+ (Some(f.body), Some(f.format.as_str().into()))
+ });
- /// Parses a matrix.org event and return a Message object
- ///
- /// # Arguments
- ///
- /// * `roomid` - The message room id
- /// * `msg` - The message event as Json
- pub fn parse_room_message(room_id: &RoomId, msg: &JsonValue) -> Result<Message, IdError> {
- let sender: UserId = msg["sender"].as_str().unwrap_or_default().try_into()?;
+ Self {
+ mtype: String::from("m.text"),
+ body,
+ formatted_body,
+ format,
+ in_reply_to,
+ replace,
+ ..initial_message
+ }
+ }
+ MessageEventContent::Emote(content) => {
+ let (formatted_body, format): (Option<String>, Option<String>) =
+ content.formatted.map_or((None, None), |f| {
+ (Some(f.body), Some(f.format.as_str().into()))
+ });
+ Self {
+ mtype: String::from("m.emote"),
+ body: content.body,
+ formatted_body,
+ format,
+ ..initial_message
+ }
+ }
+ MessageEventContent::Location(content) => Self {
+ mtype: String::from("m.location"),
+ body: content.body,
+ ..initial_message
+ },
+ MessageEventContent::Notice(content) => {
+ let (in_reply_to, replace) =
+ content.relates_to.map_or(Default::default(), |r| match r {
+ Relation::Replacement(rep) => (None, Some(rep.event_id)),
+ Relation::Reply { in_reply_to } => (Some(in_reply_to.event_id), None),
+ _ => (None, None),
+ });
+ let (body, formatted, in_reply_to) = content.new_content.map_or(
+ (content.body, content.formatted, in_reply_to),
+ |nc| {
+ let in_reply_to = nc.relates_to.and_then(|r| match r {
+ Relation::Reply { in_reply_to } => Some(in_reply_to.event_id),
+ _ => None,
+ });
- let timestamp = msg["origin_server_ts"].as_i64().unwrap_or_default() / 1000;
- let server_timestamp: DateTime<Local> = Local.timestamp(timestamp, 0);
+ (nc.body, nc.formatted, in_reply_to)
+ },
+ );
+ let (formatted_body, format) = formatted.map_or(Default::default(), |f| {
+ (Some(f.body), Some(f.format.as_str().into()))
+ });
- let id: EventId = msg["event_id"].as_str().unwrap_or_default().try_into()?;
- let type_ = msg["type"].as_str().unwrap_or_default();
+ Self {
+ mtype: String::from("m.notice"),
+ body,
+ formatted_body,
+ format,
+ in_reply_to,
+ replace,
+ ..initial_message
+ }
+ }
+ MessageEventContent::ServerNotice(content) => Self {
+ mtype: String::from("m.server_notice"),
+ body: content.body,
+ ..initial_message
+ },
+ _ => initial_message,
+ }
+ }
+}
- let redacted = msg["unsigned"].get("redacted_because") != None;
+impl From<RedactedMessageEvent<RedactedMessageEventContent>> for Message {
+ fn from(msg: RedactedMessageEvent<RedactedMessageEventContent>) -> Self {
+ let source = serde_json::to_string_pretty(&msg).ok();
- let mut message = Message {
- sender,
- date: server_timestamp,
- room: room_id.clone(),
+ Self {
+ sender: msg.sender,
+ date: msg.origin_server_ts.into(),
+ room: msg.room_id,
// It is mandatory for a message event to have
// an event_id field
- id: Some(id),
- mtype: type_.to_string(),
+ id: Some(msg.event_id),
+ mtype: String::from(msg.content.event_type()),
body: String::new(),
url: None,
local_path: None,
@@ -149,90 +250,167 @@ impl Message {
local_path_thumb: None,
formatted_body: None,
format: None,
- source: serde_json::to_string_pretty(&msg).ok(),
+ source,
receipt: HashMap::new(),
- redacted,
+ redacted: true,
in_reply_to: None,
replace: None,
extra_content: None,
- };
+ }
+ }
+}
- let c = &msg["content"];
- match type_ {
- "m.room.message" => message.parse_m_room_message(c),
- "m.sticker" => message.parse_m_sticker(c),
- _ => {}
- };
+impl From<MessageEvent<StickerEventContent>> for Message {
+ fn from(msg: MessageEvent<StickerEventContent>) -> Self {
+ let source = serde_json::to_string_pretty(&msg).ok();
+ let url = Url::parse(&msg.content.url).ok();
- Ok(message)
+ Self {
+ sender: msg.sender,
+ date: msg.origin_server_ts.into(),
+ room: msg.room_id,
+ // It is mandatory for a message event to have
+ // an event_id field
+ id: Some(msg.event_id),
+ mtype: String::from(msg.content.event_type()),
+ body: msg.content.body,
+ url: url.clone(),
+ local_path: None,
+ thumb: msg
+ .content
+ .info
+ .thumbnail_url
+ .and_then(|thumb| Url::parse(&thumb).ok())
+ .or(url),
+ local_path_thumb: None,
+ formatted_body: None,
+ format: None,
+ source,
+ receipt: HashMap::new(),
+ redacted: false,
+ in_reply_to: None,
+ replace: None,
+ extra_content: None,
+ }
}
+}
- fn parse_m_room_message(&mut self, mut c: &JsonValue) {
- let rel_type = c["m.relates_to"]["rel_type"]
- .as_str()
- .map(String::from)
- .unwrap_or_default();
-
- if rel_type == "m.replace" {
- self.replace = c["m.relates_to"]["event_id"]
- .as_str()
- .and_then(|evid| evid.try_into().ok());
- c = &c["m.new_content"];
+impl From<RedactedMessageEvent<RedactedStickerEventContent>> for Message {
+ fn from(msg: RedactedMessageEvent<RedactedStickerEventContent>) -> Self {
+ let source = serde_json::to_string_pretty(&msg).ok();
+
+ Self {
+ sender: msg.sender,
+ date: msg.origin_server_ts.into(),
+ room: msg.room_id,
+ // It is mandatory for a message event to have
+ // an event_id field
+ id: Some(msg.event_id),
+ mtype: String::from(msg.content.event_type()),
+ body: String::new(),
+ url: None,
+ local_path: None,
+ thumb: None,
+ local_path_thumb: None,
+ formatted_body: None,
+ format: None,
+ source,
+ receipt: HashMap::new(),
+ redacted: true,
+ in_reply_to: None,
+ replace: None,
+ extra_content: None,
}
+ }
+}
+
+impl TryFrom<AnyRoomEvent> for Message {
+ type Error = ();
- let mtype = c["msgtype"].as_str().map(String::from).unwrap_or_default();
- let body = c["body"].as_str().map(String::from).unwrap_or_default();
- let formatted_body = c["formatted_body"].as_str().map(String::from);
- let format = c["format"].as_str().map(String::from);
-
- match mtype.as_str() {
- "m.image" | "m.file" | "m.video" | "m.audio" => {
- self.url = c["url"].as_str().map(Url::parse).and_then(Result::ok);
- self.thumb = c["info"]["thumbnail_url"]
- .as_str()
- .map(Url::parse)
- .and_then(Result::ok)
- .or_else(|| Some(self.url.clone()?));
+ fn try_from(event: AnyRoomEvent) -> Result<Self, Self::Error> {
+ match event {
+ AnyRoomEvent::Message(AnyMessageEvent::RoomMessage(room_messages_event)) => {
+ Ok(Self::from(room_messages_event))
}
- "m.text" => {
- // Only m.text messages can be replies for backward compatibility
- // https://matrix.org/docs/spec/client_server/r0.4.0.html#rich-replies
- self.in_reply_to = c["m.relates_to"]["m.in_reply_to"]["event_id"]
- .as_str()
- .and_then(|evid| evid.try_into().ok());
+ AnyRoomEvent::Message(AnyMessageEvent::Sticker(sticker_event)) => {
+ Ok(Self::from(sticker_event))
}
- _ => {}
- };
+ AnyRoomEvent::RedactedMessage(AnyRedactedMessageEvent::RoomMessage(
+ redacted_room_messages_event,
+ )) => Ok(Self::from(redacted_room_messages_event)),
+ AnyRoomEvent::RedactedMessage(AnyRedactedMessageEvent::Sticker(
+ redacted_sticker_event,
+ )) => Ok(Self::from(redacted_sticker_event)),
+ _ => Err(()),
+ }
+ }
+}
- self.mtype = mtype;
- self.body = body;
- self.formatted_body = formatted_body;
- self.format = format;
+impl TryFrom<(RoomId, AnySyncRoomEvent)> for Message {
+ type Error = ();
+
+ fn try_from((room_id, event): (RoomId, AnySyncRoomEvent)) -> Result<Self, Self::Error> {
+ match event {
+ AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(room_messages_event)) => {
+ Ok(Self::from(room_messages_event.into_full_event(room_id)))
+ }
+ AnySyncRoomEvent::Message(AnySyncMessageEvent::Sticker(sticker_event)) => {
+ Ok(Self::from(sticker_event.into_full_event(room_id)))
+ }
+ AnySyncRoomEvent::RedactedMessage(AnyRedactedSyncMessageEvent::RoomMessage(
+ redacted_room_messages_event,
+ )) => Ok(Self::from(
+ redacted_room_messages_event.into_full_event(room_id),
+ )),
+ AnySyncRoomEvent::RedactedMessage(AnyRedactedSyncMessageEvent::Sticker(
+ redacted_sticker_event,
+ )) => Ok(Self::from(redacted_sticker_event.into_full_event(room_id))),
+ _ => Err(()),
+ }
}
+}
- fn parse_m_sticker(&mut self, c: &JsonValue) {
- self.url = c["url"].as_str().map(Url::parse).and_then(Result::ok);
- self.thumb = c["info"]["thumbnail_url"]
- .as_str()
- .map(Url::parse)
- .and_then(Result::ok)
- .or_else(|| Some(self.url.clone()?));
- self.body = c["body"].as_str().map(String::from).unwrap_or_default();
+impl Message {
+ pub fn new(
+ room: RoomId,
+ sender: UserId,
+ body: String,
+ mtype: String,
+ id: Option<EventId>,
+ ) -> Self {
+ let date = Local::now();
+ Message {
+ id,
+ sender,
+ mtype,
+ body,
+ date,
+ room,
+ thumb: None,
+ local_path_thumb: None,
+ url: None,
+ local_path: None,
+ formatted_body: None,
+ format: None,
+ source: None,
+ receipt: HashMap::new(),
+ redacted: false,
+ in_reply_to: None,
+ replace: None,
+ extra_content: None,
+ }
}
- /// Create a vec of Message from a json event list
+ /// Generates an unique transaction id for this message
+ /// The txn_id is generated using the md5sum of a concatenation of the message room id, the
+ /// message body and the date.
///
- /// * `roomid` - The messages room id
- /// * `events` - An iterator to the json events
- pub fn from_json_events<I>(room_id: &RoomId, events: I) -> Result<Vec<Message>, IdError>
- where
- I: IntoIterator<Item = JsonValue>,
- {
- events
- .into_iter()
- .filter(Message::supported_event)
- .map(|msg| Message::parse_room_message(&room_id, &msg))
- .collect()
+ ///
https://matrix.org/docs/spec/client_server/r0.3.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
+ // TODO: Return matrix_sdk::uuid::Uuid
+ pub fn get_txn_id(&self) -> String {
+ let msg_str = format!("{}{}{}", self.room, self.body, self.date);
+ let digest = md5::compute(msg_str.as_bytes());
+ format!("{:x}", digest)
}
pub fn set_receipt(&mut self, receipt: HashMap<UserId, i64>) {
diff --git a/fractal-gtk/src/model/mod.rs b/fractal-gtk/src/model/mod.rs
index 0569a8c0..6d9eedcb 100644
--- a/fractal-gtk/src/model/mod.rs
+++ b/fractal-gtk/src/model/mod.rs
@@ -1,4 +1,3 @@
-pub mod event;
pub mod fileinfo;
pub mod member;
pub mod message;
diff --git a/fractal-gtk/src/model/room.rs b/fractal-gtk/src/model/room.rs
index e11952be..a9600ccc 100644
--- a/fractal-gtk/src/model/room.rs
+++ b/fractal-gtk/src/model/room.rs
@@ -1,21 +1,23 @@
-use serde_json::Value as JsonValue;
-
-use crate::app::RUNTIME;
-use crate::backend::user::get_user_avatar;
use crate::model::member::Member;
use crate::model::member::MemberList;
use crate::model::message::Message;
+use chrono::DateTime;
+use chrono::Utc;
use either::Either;
+use fractal_api::api::r0::sync::sync_events::Response as SyncResponse;
use fractal_api::directory::PublicRoomsChunk;
-use fractal_api::identifiers::{Error as IdError, EventId, RoomAliasId, RoomId, UserId};
-use fractal_api::r0::sync::sync_events::Response as SyncResponse;
+use fractal_api::events::{
+ room::member::{MemberEventContent, MembershipState},
+ AnyBasicEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncStateEvent,
+ SyncStateEvent,
+};
+use fractal_api::identifiers::{EventId, RoomAliasId, RoomId, UserId};
use fractal_api::url::{ParseError as UrlError, Url};
-use fractal_api::Client as MatrixClient;
+use fractal_api::Error as MatrixError;
use log::{debug, info};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::convert::{TryFrom, TryInto};
-use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RoomMembership {
@@ -23,7 +25,7 @@ pub enum RoomMembership {
None,
Joined(RoomTag),
// An invite is send by some other user
- Invited(Member),
+ Invited(UserId),
Left(Reason),
}
@@ -72,7 +74,7 @@ impl Default for RoomMembership {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Reason {
None,
- Kicked(String, Member),
+ Kicked(String, UserId),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@@ -94,8 +96,8 @@ pub struct Room {
pub world_readable: bool,
pub n_members: u64,
pub members: MemberList,
- pub notifications: i32,
- pub highlight: i32,
+ pub notifications: u64,
+ pub highlight: u64,
pub messages: Vec<Message>,
pub membership: RoomMembership,
pub direct: bool,
@@ -105,8 +107,8 @@ pub struct Room {
/// Hashmap with the room users power levels
/// the key will be the userid and the value will be the level
- pub admins: HashMap<UserId, i32>,
- pub default_power_level: i32,
+ pub admins: HashMap<UserId, i64>,
+ pub default_power_level: i64,
}
impl Room {
@@ -135,106 +137,261 @@ impl Room {
}
pub fn from_sync_response(
- session_client: MatrixClient,
response: &SyncResponse,
user_id: UserId,
- ) -> Result<Vec<Self>, IdError> {
+ ) -> Result<Vec<Self>, MatrixError> {
// getting the list of direct rooms
- let direct: HashSet<RoomId> = parse_m_direct(&response.account_data.events)
- .values()
+ let direct: HashSet<RoomId> = response.account_data.events
+ .iter()
+ // Synapse sometimes sends an object with the key "[object Object]"
+ // instead of a user ID, so we have to skip those invalid objects
+ // in the array in order to avoid discarding everything
+ .filter_map(|ev| ev.deserialize().ok())
+ .find_map(|event| match event {
+ AnyBasicEvent::Direct(ev) => Some(ev.content.0),
+ _ => None,
+ })
+ .into_iter()
.flatten()
- .cloned()
+ .flat_map(|(_, rids)| rids)
.collect();
+ /*
+ response.rooms.join.iter().for_each(|(_, room)| {
+ room.state.events.iter().for_each(|ev| log::info!("stevents: {}", ev.json().get()));
+ });
+ */
+
let joined_rooms = response.rooms.join.iter().map(|(k, room)| {
- let stevents = &room.state.events;
- let timeline = &room.timeline;
- let ephemeral = &room.ephemeral;
- let dataevs = &room.account_data.events;
+ let stevents = room
+ .state
+ .events
+ .iter()
+ .map(|ev| ev.deserialize())
+ .collect::<Result<Vec<_>, _>>()?;
+ let dataevs = room
+ .account_data
+ .events
+ .iter()
+ .map(|ev| ev.deserialize())
+ .collect::<Result<Vec<_>, _>>()?;
let room_tag = dataevs
.iter()
- .filter(|x| x["type"] == "m.tag")
- .find_map(|tag| tag["content"]["tags"]["m.favourite"].as_object())
+ .find_map(|event| match event {
+ AnyBasicEvent::Tag(ev) => ev.content.tags.get("m.favourite"),
+ _ => None,
+ })
.and(Some(RoomTag::Favourite))
.unwrap_or(RoomTag::None);
- let room_lang = dataevs
+
+ let members: MemberList = stevents
.iter()
- .filter(|x| x["type"] == "org.gnome.fractal.language")
- .find_map(|entry| entry["content"]["input_language"].as_str())
- .map(|lang| lang.to_string());
+ .filter_map(|event| match event {
+ AnySyncStateEvent::RoomMember(ev) => parse_room_member(ev),
+ _ => None,
+ })
+ .map(|m| (m.uid.clone(), m))
+ .collect();
let mut r = Self {
- name: calculate_room_name(stevents, &user_id),
- avatar: evc(stevents, "m.room.avatar", "url").and_then(|url| Url::parse(&url).ok()),
- alias: evc(stevents, "m.room.canonical_alias", "alias")
- .and_then(|alias| RoomAliasId::try_from(alias).ok()),
- topic: evc(stevents, "m.room.topic", "topic"),
+ name: stevents
+ .iter()
+ .filter_map(|event| match event {
+ AnySyncStateEvent::RoomName(ev) => {
+ ev.content.name().filter(|name| !name.is_empty()).map(Err)
+ }
+ AnySyncStateEvent::RoomCanonicalAlias(ev) => ev
+ .content
+ .alias
+ .as_ref()
+ .map(|r_alias| Ok(r_alias.as_str())),
+ _ => None,
+ })
+ .try_fold(None, |_, alias_name| alias_name.map(Some))
+ .unwrap_or_else(Some)
+ .map(Into::into)
+ .or_else(|| {
+ let members: Vec<_> = members
+ .values()
+ .map(|m| m.alias.as_deref().unwrap_or_else(|| m.uid.as_str()))
+ .filter(|&uid| uid == user_id.as_str())
+ .collect();
+ room_name_from_members(&members)
+ }),
+ avatar: stevents
+ .iter()
+ .find_map(|event| match event {
+ AnySyncStateEvent::RoomAvatar(ev) => Some(ev.content.url.as_deref()),
+ _ => None,
+ })
+ .flatten()
+ .map(Url::parse)
+ .and_then(Result::ok),
+ alias: stevents
+ .iter()
+ .find_map(|event| match event {
+ AnySyncStateEvent::RoomCanonicalAlias(ev) => Some(ev.content.alias.clone()),
+ _ => None,
+ })
+ .flatten(),
+ topic: stevents.iter().find_map(|event| match event {
+ AnySyncStateEvent::RoomTopic(ev) => Some(ev.content.topic.clone()),
+ _ => None,
+ }),
direct: direct.contains(&k),
- notifications: room.unread_notifications.notification_count,
- highlight: room.unread_notifications.highlight_count,
- prev_batch: timeline.prev_batch.clone(),
- messages: Message::from_json_events(&k, timeline.events.clone())?,
- admins: get_admins(stevents)?,
- default_power_level: get_default_power_level(stevents),
- members: stevents
+ notifications: room
+ .unread_notifications
+ .notification_count
+ .map(Into::into)
+ .unwrap_or_default(),
+ highlight: room
+ .unread_notifications
+ .highlight_count
+ .map(Into::into)
+ .unwrap_or_default(),
+ prev_batch: room.timeline.prev_batch.clone(),
+ messages: room
+ .timeline
+ .events
+ .iter()
+ .map(|ev| Ok((k.clone(), ev.deserialize()?)))
+ .filter_map(|k_ev| k_ev.map(TryInto::try_into).map(Result::ok).transpose())
+ .collect::<Result<Vec<_>, MatrixError>>()?,
+ admins: stevents
.iter()
- .filter(|x| x["type"] == "m.room.member")
- .filter_map(parse_room_member)
- .map(|m| (m.uid.clone(), m))
+ .filter_map(|event| match event {
+ AnySyncStateEvent::RoomPowerLevels(ev) => Some(ev.content.users.clone()),
+ _ => None,
+ })
+ .flatten()
+ .map(|(uid, level)| (uid, level.into()))
.collect(),
- language: room_lang,
+ default_power_level: stevents
+ .iter()
+ .filter_map(|event| match event {
+ AnySyncStateEvent::RoomPowerLevels(ev) => {
+ Some(ev.content.users_default.clone().into())
+ }
+ _ => None,
+ })
+ .last()
+ .unwrap_or(-1),
+ members,
+ language: dataevs
+ .iter()
+ .find_map(|event| match event {
+ AnyBasicEvent::Custom(ev)
+ if ev.content.event_type == "org.gnome.fractal.language" =>
+ {
+ ev.content.json["input_language"].as_str()
+ }
+ _ => None,
+ })
+ .map(String::from),
..Self::new(k.clone(), RoomMembership::Joined(room_tag))
};
- r.add_receipt_from_json(
- ephemeral
- .events
- .iter()
- .filter(|ev| ev["type"] == "m.receipt")
- .collect(),
- );
- // Adding fully read to the receipts events
- if let Some(ev) = dataevs
+ let receipts: HashMap<EventId, HashMap<UserId, i64>> = room
+ .ephemeral
+ .events
+ .iter()
+ .find_map(|event| match event.deserialize() {
+ Ok(AnySyncEphemeralRoomEvent::Receipt(ev)) => Some(Ok(ev.content.0)),
+ Ok(_) => None,
+ Err(err) => Some(Err(err)),
+ })
+ .transpose()?
+ .into_iter()
+ .flatten()
+ .map(|(event_id, receipts)| {
+ let receipts = receipts
+ .read
+ .into_iter()
+ .flatten()
+ .map(|(uid, receipt)| {
+ let ts = receipt
+ .ts
+ .map(DateTime::<Utc>::from)
+ .map(|time| time.timestamp())
+ .unwrap_or_default();
+ (uid, ts)
+ })
+ .inspect(|(_, ts)| {
+ debug!("Value of timestamp 'ts': {:?}", ts);
+ if *ts == 0 {
+ info!(
+ "Possibly malformed timestamp, working around synapse bug 4898"
+ );
+ };
+ })
+ .collect();
+
+ (event_id, receipts)
+ })
+ .collect();
+
+ r.messages
+ .iter_mut()
+ .filter_map(|msg| {
+ let receipt = msg
+ .id
+ .as_ref()
+ .and_then(|evid| receipts.get(evid))
+ .cloned()?;
+ Some((msg, receipt))
+ })
+ .for_each(|(msg, receipt)| msg.set_receipt(receipt));
+
+ if let Some(event_id) = room
+ .ephemeral
+ .events
.iter()
- .find(|x| x["type"] == "m.fully_read")
- .and_then(|fread| fread["content"]["event_id"].as_str()?.try_into().ok())
+ .find_map(|event| match event.deserialize() {
+ Ok(AnySyncEphemeralRoomEvent::FullyRead(ev)) => Some(Ok(ev.content.event_id)),
+ Ok(_) => None,
+ Err(err) => Some(Err(err)),
+ })
+ .transpose()?
{
- r.add_receipt_from_fully_read(user_id.clone(), ev);
+ let event_id = Some(event_id);
+
+ r.messages
+ .iter_mut()
+ .filter(|msg| msg.id == event_id)
+ .for_each(|msg| {
+ msg.receipt.insert(user_id.clone(), 0);
+ });
}
Ok(r)
});
let left_rooms = response.rooms.leave.iter().map(|(k, room)| {
- let r = if let Some(last_event) = room.timeline.events.last() {
- let leave_id = UserId::try_from(last_event["sender"].as_str().unwrap_or_default())?;
- if leave_id != user_id {
+ // TODO: The spec doesn't explain where to get the reason
+ // for the kicking from, so matrix-sdk doesn't support
+ // that.
+ if let Some(last_event) = room
+ .timeline
+ .events
+ .last()
+ .map(|ev| serde_json::to_value(ev.json()))
+ .transpose()?
+ {
+ if let Some(kicker) =
+ UserId::try_from(last_event["sender"].as_str().unwrap_or_default())
+ .ok()
+ .filter(|leave_uid| *leave_uid != user_id)
+ {
let kick_reason = &last_event["content"]["reason"];
- if let Ok((kicker_alias, kicker_avatar)) = RUNTIME
- .handle()
- .block_on(get_user_avatar(session_client.clone(), &leave_id))
- {
- let kicker = Member {
- alias: Some(kicker_alias),
- avatar: Some(Either::Right(kicker_avatar)),
- uid: leave_id,
- };
- let reason = Reason::Kicked(
- String::from(kick_reason.as_str().unwrap_or_default()),
- kicker,
- );
- Self::new(k.clone(), RoomMembership::Left(reason))
- } else {
- Self::new(k.clone(), RoomMembership::Left(Reason::None))
- }
- } else {
- Self::new(k.clone(), RoomMembership::Left(Reason::None))
+ let reason = Reason::Kicked(
+ String::from(kick_reason.as_str().unwrap_or_default()),
+ kicker,
+ );
+ return Ok(Self::new(k.clone(), RoomMembership::Left(reason)));
}
- } else {
- Self::new(k.clone(), RoomMembership::Left(Reason::None))
};
- Ok(r)
+ Ok(Self::new(k.clone(), RoomMembership::Left(Reason::None)))
});
let invited_rooms = response
@@ -242,34 +399,75 @@ impl Room {
.invite
.iter()
.map(|(k, room)| {
- let stevents = &room.invite_state.events;
- let alias_avatar: Result<Option<(String, PathBuf)>, IdError> = stevents
+ let stevents = room
+ .invite_state
+ .events
.iter()
- .find(|x| {
- x["content"]["membership"] == "invite"
- && x["state_key"] == user_id.to_string().as_str()
+ .map(|ev| ev.deserialize())
+ .collect::<Result<Vec<_>, _>>()?;
+ let inv_sender = stevents
+ .iter()
+ .find_map(|event| match event {
+ AnyStrippedStateEvent::RoomMember(ev)
+ if ev.content.membership == MembershipState::Invite
+ && ev.state_key == user_id =>
+ {
+ Some(ev)
+ }
+ _ => None,
})
- .map_or(Ok(None), |ev| {
- let user_id = UserId::try_from(ev["sender"].as_str().unwrap_or_default())?;
- let avatar = RUNTIME
- .handle()
- .block_on(get_user_avatar(session_client.clone(), &user_id));
- Ok(avatar.ok())
- });
- if let Some((alias, avatar)) = alias_avatar? {
- let inv_sender = Member {
- alias: Some(alias),
- avatar: Some(Either::Right(avatar)),
- uid: user_id.clone(),
- };
-
+ .map(|ev| ev.sender.clone());
+ if let Some(inv_sender) = inv_sender {
Ok(Some(Self {
- name: calculate_room_name(stevents, &user_id),
- avatar: evc(stevents, "m.room.avatar", "url")
- .and_then(|url| Url::parse(&url).ok()),
- alias: evc(stevents, "m.room.canonical_alias", "alias")
- .and_then(|alias| RoomAliasId::try_from(alias).ok()),
- topic: evc(stevents, "m.room.topic", "topic"),
+ name: stevents
+ .iter()
+ .filter_map(|event| match event {
+ AnyStrippedStateEvent::RoomName(ev) => {
+ ev.content.name().filter(|name| !name.is_empty()).map(Err)
+ }
+ AnyStrippedStateEvent::RoomCanonicalAlias(ev) => ev
+ .content
+ .alias
+ .as_ref()
+ .map(|r_alias| Ok(r_alias.as_str())),
+ _ => None,
+ })
+ .try_fold(None, |_, alias_name| alias_name.map(Some))
+ .unwrap_or_else(Some)
+ .map(Into::into)
+ .or_else(|| {
+ let members: Vec<_> = stevents
+ .iter()
+ .filter_map(|event| member_from_stripped_event(event, &user_id))
+ .take(3)
+ .map(Into::into)
+ .collect();
+ room_name_from_members(&members)
+ }),
+ avatar: stevents
+ .iter()
+ .find_map(|event| match event {
+ AnyStrippedStateEvent::RoomAvatar(ev) => {
+ Some(ev.content.url.as_deref())
+ }
+ _ => None,
+ })
+ .flatten()
+ .map(Url::parse)
+ .and_then(Result::ok),
+ alias: stevents
+ .iter()
+ .find_map(|event| match event {
+ AnyStrippedStateEvent::RoomCanonicalAlias(ev) => {
+ Some(ev.content.alias.clone())
+ }
+ _ => None,
+ })
+ .flatten(),
+ topic: stevents.iter().find_map(|event| match event {
+ AnyStrippedStateEvent::RoomTopic(ev) => Some(ev.content.topic.clone()),
+ _ => None,
+ }),
direct: direct.contains(&k),
..Self::new(k.clone(), RoomMembership::Invited(inv_sender))
}))
@@ -284,49 +482,6 @@ impl Room {
.chain(invited_rooms)
.collect()
}
-
- pub fn add_receipt_from_json(&mut self, mut events: Vec<&JsonValue>) {
- let receipts: HashMap<EventId, HashMap<UserId, i64>> = events
- .pop()
- .and_then(|ev| ev["content"].as_object())
- .into_iter()
- .flatten()
- .filter_map(|(mid, obj)| {
- let event_id = mid.as_str().try_into().ok()?;
- let receipts = obj["m.read"]
- .as_object()?
- .iter()
- .map(|(uid, ts)| {
- debug!("Value of timestamp 'ts': {}", ts);
- let ts = ts["ts"].as_i64().unwrap_or(0);
- if ts == 0 {
- info!("Possibly malformed timestamp, working around synapse bug 4898");
- };
- Ok((UserId::try_from(uid.as_str())?, ts))
- })
- .collect::<Result<HashMap<UserId, i64>, IdError>>()
- .ok()?;
-
- Some((event_id, receipts))
- })
- .collect();
-
- for msg in self.messages.iter_mut() {
- if let Some(r) = msg.id.as_ref().and_then(|evid| receipts.get(evid)) {
- msg.set_receipt(r.clone());
- }
- }
- }
-
- pub fn add_receipt_from_fully_read(&mut self, uid: UserId, event_id: EventId) {
- let event_id = Some(event_id);
-
- let _ = self
- .messages
- .iter_mut()
- .filter(|msg| msg.id == event_id)
- .map(|msg| msg.receipt.insert(uid.clone(), 0));
- }
}
impl TryFrom<PublicRoomsChunk> for Room {
@@ -358,121 +513,56 @@ impl PartialEq for Room {
pub type RoomList = HashMap<RoomId, Room>;
-fn evc(events: &[JsonValue], t: &str, field: &str) -> Option<String> {
- events
- .iter()
- .find(|x| x["type"] == t)
- .and_then(|js| js["content"][field].as_str())
- .map(Into::into)
-}
-
-fn get_admins(stevents: &[JsonValue]) -> Result<HashMap<UserId, i32>, IdError> {
- stevents
- .iter()
- .filter(|x| x["type"] == "m.room.power_levels")
- .filter_map(|ev| ev["content"]["users"].as_object())
- .flatten()
- .map(|(k, v)| {
- Ok((
- UserId::try_from(k.as_str())?,
- v.as_i64().map(|v| v as i32).unwrap_or_default(),
- ))
- })
- .collect()
-}
-
-fn get_default_power_level(stevents: &[JsonValue]) -> i32 {
- stevents
- .iter()
- .filter(|x| x["type"] == "m.room.power_levels")
- .filter_map(|ev| ev["content"]["users_default"].as_i64())
- .last()
- .unwrap_or(-1) as i32
-}
-
-fn calculate_room_name(events: &[JsonValue], user_id: &UserId) -> Option<String> {
- let userid = user_id.to_string();
- // looking for "m.room.name" event
- if let Some(name) = events
- .iter()
- .find(|x| x["type"] == "m.room.name")
- .and_then(|name| name["content"]["name"].as_str())
- .filter(|name| !name.is_empty())
- .map(Into::into)
- {
- return Some(name);
- }
-
- // looking for "m.room.canonical_alias" event
- if let Some(name) = events
- .iter()
- .find(|x| x["type"] == "m.room.canonical_alias")
- .and_then(|name| name["content"]["alias"].as_str())
- .map(Into::into)
- {
- return Some(name);
+fn member_from_stripped_event<'a>(
+ event: &'a AnyStrippedStateEvent,
+ user_id: &UserId,
+) -> Option<&'a str> {
+ match event {
+ AnyStrippedStateEvent::RoomMember(ev) => match ev.content.membership {
+ MembershipState::Join if ev.sender.as_str() != user_id.as_str() => Some(
+ ev.content
+ .displayname
+ .as_ref()
+ .map(String::as_str)
+ .unwrap_or_else(|| ev.sender.as_str()),
+ ),
+ MembershipState::Invite if ev.state_key.as_str() != user_id.as_str() => Some(
+ ev.content
+ .displayname
+ .as_ref()
+ .map(String::as_str)
+ .unwrap_or_else(|| ev.state_key.as_str()),
+ ),
+ _ => None,
+ },
+ _ => None,
}
+}
- // we look for members that aren't me
- let members: Vec<&str> = events
- .iter()
- .filter(|x| {
- x["type"] == "m.room.member"
- && ((x["content"]["membership"] == "join" && x["sender"] != userid.as_str())
- || (x["content"]["membership"] == "invite"
- && x["state_key"] != userid.as_str()))
- })
- .take(3)
- .map(|m| {
- let sender = m["sender"].as_str().unwrap_or("NONAMED");
- m["content"]["displayname"].as_str().unwrap_or(sender)
- })
- .collect();
-
+fn room_name_from_members(members: &[&str]) -> Option<String> {
match members.len() {
- // we don't have information to calculate the name
0 => None,
- 1 => Some(members[0].to_string()),
+ 1 => Some(members[0].to_owned()),
2 => Some(format!("{} and {}", members[0], members[1])),
_ => Some(format!("{} and Others", members[0])),
}
}
-fn parse_room_member(msg: &JsonValue) -> Option<Member> {
- let c = &msg["content"];
- let _ = c["membership"].as_str().filter(|&m| m == "join")?;
-
- Some(Member {
- uid: msg["sender"].as_str().unwrap_or_default().try_into().ok()?,
- alias: c["displayname"].as_str().map(String::from),
- avatar: c["avatar_url"]
- .as_str()
- .map(Url::parse)
- .and_then(Result::ok)
- .map(Either::Left),
- })
-}
-
-fn parse_m_direct(events: &[JsonValue]) -> HashMap<UserId, Vec<RoomId>> {
- events
- .iter()
- .find(|x| x["type"] == "m.direct")
- .and_then(|js| js["content"].as_object())
- .cloned()
- .unwrap_or_default()
- .iter()
- // Synapse sometimes sends an object with the key "[object Object]"
- // instead of a user ID, so we have to skip those invalid objects
- // in the array in order to avoid discarding everything
- .filter_map(|(uid, rid)| {
- let value = rid
- .as_array()
- .unwrap_or(&vec![])
- .iter()
- .map(|rid| RoomId::try_from(rid.as_str().unwrap_or_default()))
- .collect::<Result<Vec<RoomId>, IdError>>()
- .ok()?;
- Some((UserId::try_from(uid.as_str()).ok()?, value))
+fn parse_room_member(msg: &SyncStateEvent<MemberEventContent>) -> Option<Member> {
+ if msg.content.membership == MembershipState::Join {
+ Some(Member {
+ uid: msg.sender.clone(),
+ alias: msg.content.displayname.clone(),
+ avatar: msg
+ .content
+ .avatar_url
+ .as_ref()
+ .map(String::as_str)
+ .map(Url::parse)
+ .and_then(Result::ok)
+ .map(Either::Left),
})
- .collect()
+ } else {
+ None
+ }
}
diff --git a/fractal-gtk/src/passwd.rs b/fractal-gtk/src/passwd.rs
index 44b6a44c..741f36ba 100644
--- a/fractal-gtk/src/passwd.rs
+++ b/fractal-gtk/src/passwd.rs
@@ -1,4 +1,3 @@
-use crate::derror;
use fractal_api::identifiers::{Error as IdError, UserId};
use fractal_api::r0::AccessToken;
use fractal_api::url::ParseError;
@@ -11,6 +10,12 @@ pub enum Error {
IdParseError(IdError),
}
+impl From<secret_service::SsError> for Error {
+ fn from(_: secret_service::SsError) -> Error {
+ Error::SecretServiceError
+ }
+}
+
impl From<ParseError> for Error {
fn from(err: ParseError) -> Error {
Error::UrlParseError(err)
@@ -23,8 +28,6 @@ impl From<IdError> for Error {
}
}
-derror!(secret_service::SsError, Error::SecretServiceError);
-
pub trait PasswordStorage {
fn delete_pass(&self, key: &str) -> Result<(), Error> {
ss_storage::delete_pass(key)
diff --git a/fractal-gtk/src/widgets/media_viewer.rs b/fractal-gtk/src/widgets/media_viewer.rs
index 6e22a74e..57f8e635 100644
--- a/fractal-gtk/src/widgets/media_viewer.rs
+++ b/fractal-gtk/src/widgets/media_viewer.rs
@@ -129,7 +129,7 @@ struct Data {
server_url: Url,
access_token: AccessToken,
uid: UserId,
- admins: HashMap<UserId, i32>,
+ admins: HashMap<UserId, i64>,
widget: Widget,
media_list: Vec<Message>,
diff --git a/fractal-gtk/src/widgets/members_list.rs b/fractal-gtk/src/widgets/members_list.rs
index 69f2f729..f1a44ac1 100644
--- a/fractal-gtk/src/widgets/members_list.rs
+++ b/fractal-gtk/src/widgets/members_list.rs
@@ -18,13 +18,13 @@ pub struct MembersList {
search_entry: gtk::SearchEntry,
error: gtk::Label,
members: Vec<Member>,
- admins: HashMap<UserId, i32>,
+ admins: HashMap<UserId, i64>,
}
impl MembersList {
pub fn new(
members: Vec<Member>,
- admins: HashMap<UserId, i32>,
+ admins: HashMap<UserId, i64>,
search_entry: gtk::SearchEntry,
) -> MembersList {
MembersList {
@@ -96,7 +96,7 @@ impl MembersList {
}
}
-fn create_row(member: Member, power_level: Option<i32>) -> Option<gtk::ListBoxRow> {
+fn create_row(member: Member, power_level: Option<i64>) -> Option<gtk::ListBoxRow> {
let row = gtk::ListBoxRow::new();
row.connect_draw(clone!(@strong member => move |w, _| {
if w.get_child().is_none() {
@@ -111,7 +111,7 @@ fn create_row(member: Member, power_level: Option<i32>) -> Option<gtk::ListBoxRo
}
/* creating the row is quite slow, therefore we have a small delay when scrolling the members list */
-fn load_row_content(member: Member, power_level: Option<i32>) -> gtk::Box {
+fn load_row_content(member: Member, power_level: Option<i64>) -> gtk::Box {
let b = gtk::Box::new(gtk::Orientation::Horizontal, 12);
// Power level badge colour
@@ -188,7 +188,7 @@ fn load_row_content(member: Member, power_level: Option<i32>) -> gtk::Box {
fn add_rows(
container: gtk::ListBox,
members: Vec<Member>,
- admins: HashMap<UserId, i32>,
+ admins: HashMap<UserId, i64>,
) -> Option<usize> {
/* Load just enough members to fill atleast the visible list */
for member in members.iter() {
diff --git a/fractal-gtk/src/widgets/roomlist.rs b/fractal-gtk/src/widgets/roomlist.rs
index f8509cb0..91a9f0e4 100644
--- a/fractal-gtk/src/widgets/roomlist.rs
+++ b/fractal-gtk/src/widgets/roomlist.rs
@@ -190,7 +190,7 @@ impl RoomListGroup {
.count()
}
- pub fn set_room_notifications(&mut self, room_id: RoomId, n: i32, h: i32) {
+ pub fn set_room_notifications(&mut self, room_id: RoomId, n: u64, h: u64) {
if let Some(ref mut r) = self.rooms.get_mut(&room_id) {
r.set_notifications(n, h);
}
@@ -645,7 +645,7 @@ impl RoomList {
+ self.rooms.get().rooms_with_notifications()
}
- pub fn set_room_notifications(&mut self, room_id: RoomId, n: i32, h: i32) {
+ pub fn set_room_notifications(&mut self, room_id: RoomId, n: u64, h: u64) {
run_in_group!(self, &room_id, set_room_notifications, room_id, n, h);
}
diff --git a/fractal-gtk/src/widgets/roomrow.rs b/fractal-gtk/src/widgets/roomrow.rs
index e2fa4fe0..5628b858 100644
--- a/fractal-gtk/src/widgets/roomrow.rs
+++ b/fractal-gtk/src/widgets/roomrow.rs
@@ -76,7 +76,7 @@ impl RoomRow {
rr
}
- pub fn set_notifications(&mut self, n: i32, h: i32) {
+ pub fn set_notifications(&mut self, n: u64, h: u64) {
self.room.notifications = n;
self.room.highlight = h;
self.notifications.set_text(&format!("{}", n));
diff --git a/fractal-matrix-api/src/meson.build b/fractal-matrix-api/src/meson.build
index 27307166..2c252dd4 100644
--- a/fractal-matrix-api/src/meson.build
+++ b/fractal-matrix-api/src/meson.build
@@ -9,12 +9,9 @@ api_sources = files(
'r0/contact/create.rs',
'r0/contact/delete.rs',
'r0/server/domain_info.rs',
- 'r0/sync/sync_events.rs',
'r0/account.rs',
'r0/contact.rs',
- 'r0/filter.rs',
'r0/server.rs',
- 'r0/sync.rs',
'identity.rs',
'lib.rs',
'r0.rs',
diff --git a/fractal-matrix-api/src/r0.rs b/fractal-matrix-api/src/r0.rs
index fc097102..f90c93a5 100644
--- a/fractal-matrix-api/src/r0.rs
+++ b/fractal-matrix-api/src/r0.rs
@@ -1,8 +1,6 @@
pub mod account;
pub mod contact;
-pub mod filter;
pub mod server;
-pub mod sync;
use serde::{Deserialize, Serialize, Serializer};
use std::convert::TryFrom;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]