[fractal] Backend: Implement and use sync response types and cleanup
- From: Jordan Petridis <jpetridis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] Backend: Implement and use sync response types and cleanup
- Date: Sat, 26 Jan 2019 17:47:36 +0000 (UTC)
commit 3194c13f1da150c454484e9c5690dcbd07082df0
Author: Alejandro DomÃnguez <adomu net-c com>
Date: Mon Jan 21 22:43:38 2019 +0100
Backend: Implement and use sync response types and cleanup
We implemented the structs to which we parse the sync response except
the events, which are a tough case for another commit. Some enhancements
and cleanups were made to the code surrounding the sync management in
order to be able to remove unnecessary error handling and/or queries.
fractal-matrix-api/src/backend/sync.rs | 118 +++++-----
fractal-matrix-api/src/model/mod.rs | 1 +
fractal-matrix-api/src/model/room.rs | 228 +++++++++++++++++-
fractal-matrix-api/src/model/sync.rs | 125 ++++++++++
fractal-matrix-api/src/types.rs | 3 +
fractal-matrix-api/src/util.rs | 414 ++-------------------------------
6 files changed, 434 insertions(+), 455 deletions(-)
---
diff --git a/fractal-matrix-api/src/backend/sync.rs b/fractal-matrix-api/src/backend/sync.rs
index f73163f6..34a87a3a 100644
--- a/fractal-matrix-api/src/backend/sync.rs
+++ b/fractal-matrix-api/src/backend/sync.rs
@@ -2,13 +2,13 @@ use crate::backend::types::BKResponse;
use crate::backend::types::Backend;
use crate::error::Error;
use crate::globals;
+use crate::types::Event;
+use crate::types::Message;
use crate::types::Room;
-use crate::util::get_rooms_from_json;
-use crate::util::get_rooms_notifies_from_json;
-use crate::util::get_rooms_timeline_from_json;
+use crate::types::SyncResponse;
+use crate::types::UnreadNotificationsCount;
use crate::util::json_q;
use crate::util::parse_m_direct;
-use crate::util::parse_sync_events;
use log::error;
use serde_json::json;
use serde_json::Value as JsonValue;
@@ -67,31 +67,53 @@ pub fn sync(bk: &Backend, new_since: Option<String>, initial: bool) -> Result<()
&url,
&attrs,
|r: JsonValue| {
- let next_batch: String = r["next_batch"].as_str().map(Into::into).unwrap_or_default();
- if let Some(since) = since {
- // New rooms
- match get_rooms_from_json(&r, &userid, &baseu) {
- Ok(rs) => tx.send(BKResponse::NewRooms(rs)).unwrap(),
- Err(err) => tx.send(BKResponse::SyncError(err)).unwrap(),
- };
-
- // Message events
- match get_rooms_timeline_from_json(&baseu, &r, &tk, &since) {
- Ok(msgs) => tx.send(BKResponse::RoomMessages(msgs)).unwrap(),
- Err(err) => tx.send(BKResponse::RoomMessagesError(err)).unwrap(),
- };
- // Room notifications
- if let Ok(notifies) = get_rooms_notifies_from_json(&r) {
- for (r, n, h) in notifies {
- tx.send(BKResponse::RoomNotifications(r.clone(), n, h))
+ if let Ok(response) = serde_json::from_value::<SyncResponse>(r) {
+ if since.is_some() {
+ let join = &response.rooms.join;
+
+ // New rooms
+ let rs = Room::from_sync_response(&response, &userid, &baseu);
+ tx.send(BKResponse::NewRooms(rs)).unwrap();
+
+ // Message events
+ let msgs = join
+ .iter()
+ .flat_map(|(k, room)| {
+ let events = room.timeline.events.iter();
+ Message::from_json_events_iter(&k, events).into_iter()
+ })
+ .collect();
+ tx.send(BKResponse::RoomMessages(msgs)).unwrap();
+
+ // Room notifications
+ for (k, room) in join.iter() {
+ let UnreadNotificationsCount {
+ highlight_count: h,
+ notification_count: n,
+ } = room.unread_notifications;
+ tx.send(BKResponse::RoomNotifications(k.clone(), n, h))
.unwrap();
}
- };
- // Other events
- match parse_sync_events(&r) {
- Err(err) => tx.send(BKResponse::SyncError(err)).unwrap(),
- Ok(events) => {
- for ev in events {
+
+ // Other events
+ join.iter()
+ .flat_map(|(k, room)| {
+ room.timeline
+ .events
+ .iter()
+ .filter(|x| x["type"] != "m.room.message")
+ .map(move |ev| Event {
+ room: k.clone(),
+ sender: ev["sender"]
+ .as_str()
+ .map(Into::into)
+ .unwrap_or_default(),
+ content: ev["content"].clone(),
+ stype: ev["type"].as_str().map(Into::into).unwrap_or_default(),
+ id: ev["id"].as_str().map(Into::into).unwrap_or_default(),
+ })
+ })
+ .for_each(|ev| {
match ev.stype.as_ref() {
"m.room.name" => {
let name = ev.content["name"]
@@ -121,35 +143,25 @@ pub fn sync(bk: &Backend, new_since: Option<String>, initial: bool) -> Result<()
error!("EVENT NOT MANAGED: {:?}", ev);
}
}
- }
- }
- };
- } else {
- data.lock().unwrap().m_direct = parse_m_direct(&r);
-
- let rooms = match get_rooms_from_json(&r, &userid, &baseu) {
- Ok(rs) => rs,
- Err(err) => {
- tx.send(BKResponse::SyncError(err)).unwrap();
- Default::default()
- }
- };
-
- let mut def: Option<Room> = None;
- let jtr = data.lock().unwrap().join_to_room.clone();
- if !jtr.is_empty() {
- if let Some(r) = rooms.iter().find(|x| x.id == jtr) {
- def = Some(r.clone());
- }
+ });
+ } else {
+ data.lock().unwrap().m_direct = parse_m_direct(&response.account_data.events);
+
+ let rooms = Room::from_sync_response(&response, &userid, &baseu);
+ let jtr = data.lock().unwrap().join_to_room.clone();
+ let def = if !jtr.is_empty() {
+ rooms.iter().find(|x| x.id == jtr).cloned()
+ } else {
+ None
+ };
+ tx.send(BKResponse::Rooms(rooms, def)).unwrap();
}
- tx.send(BKResponse::Rooms(rooms, def)).unwrap();
- }
- tx.send(BKResponse::Sync(next_batch.clone())).unwrap();
- data.lock().unwrap().since = if !next_batch.is_empty() {
- Some(next_batch)
+ let next_batch = response.next_batch;
+ tx.send(BKResponse::Sync(next_batch.clone())).unwrap();
+ data.lock().unwrap().since = Some(next_batch).filter(|s| !s.is_empty());
} else {
- None
+ tx.send(BKResponse::SyncError(Error::BackendError)).unwrap();
}
},
|err| {
diff --git a/fractal-matrix-api/src/model/mod.rs b/fractal-matrix-api/src/model/mod.rs
index 07faeeb8..956385fd 100644
--- a/fractal-matrix-api/src/model/mod.rs
+++ b/fractal-matrix-api/src/model/mod.rs
@@ -5,4 +5,5 @@ pub mod protocol;
pub mod register;
pub mod room;
pub mod stickers;
+pub mod sync;
pub mod userinfo;
diff --git a/fractal-matrix-api/src/model/room.rs b/fractal-matrix-api/src/model/room.rs
index 3bdcf6e3..c57c15b8 100644
--- a/fractal-matrix-api/src/model/room.rs
+++ b/fractal-matrix-api/src/model/room.rs
@@ -3,8 +3,12 @@ use serde_json::Value as JsonValue;
use crate::model::member::Member;
use crate::model::member::MemberList;
use crate::model::message::Message;
+use crate::types::SyncResponse;
+use crate::util::get_user_avatar;
+use crate::util::parse_m_direct;
use serde::{Deserialize, Serialize};
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
+use url::Url;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RoomMembership {
@@ -99,6 +103,114 @@ impl Room {
}
}
+ pub fn from_sync_response(r: &SyncResponse, userid: &str, baseu: &Url) -> Vec<Self> {
+ let join = r.rooms.join.clone();
+ let leave = r.rooms.leave.clone();
+ let invite = r.rooms.invite.clone();
+
+ // getting the list of direct rooms
+ let direct: HashSet<String> = parse_m_direct(&r.account_data.events)
+ .values()
+ .flatten()
+ .cloned()
+ .collect();
+
+ let mut rooms: Vec<Self> = vec![];
+ for (k, room) in join.iter() {
+ let stevents = &room.state.events;
+ let timeline = &room.timeline;
+ let ephemeral = &room.ephemeral;
+ let dataevs = &room.account_data.events;
+ let name = calculate_room_name(stevents, userid);
+ let mut room_tag = RoomTag::None;
+ for tag in dataevs.iter().filter(|x| x["type"] == "m.tag") {
+ if tag["content"]["tags"]["m.favourite"].as_object().is_some() {
+ room_tag = RoomTag::Favourite;
+ }
+ }
+ let mut r = Self::new(k.clone(), RoomMembership::Joined(room_tag));
+
+ r.name = name;
+ r.avatar = Some(evc(stevents, "m.room.avatar", "url"));
+ r.alias = Some(evc(stevents, "m.room.canonical_alias", "alias"));
+ r.topic = Some(evc(stevents, "m.room.topic", "topic"));
+ r.direct = direct.contains(k);
+ r.notifications = room.unread_notifications.notification_count;
+ r.highlight = room.unread_notifications.highlight_count;
+
+ r.prev_batch = timeline.prev_batch.clone();
+
+ let ms = Message::from_json_events_iter(&k, timeline.events.iter());
+ r.messages.extend(ms);
+
+ 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(fread) = dataevs.into_iter().find(|x| x["type"] == "m.fully_read") {
+ if let Some(ev) = fread["content"]["event_id"].as_str() {
+ r.add_receipt_from_fully_read(userid, ev);
+ }
+ }
+
+ let mevents = stevents.iter().filter(|x| x["type"] == "m.room.member");
+
+ for ev in mevents {
+ let member = parse_room_member(ev);
+ if let Some(m) = member {
+ r.members.insert(m.uid.clone(), m.clone());
+ }
+ }
+
+ // power levels info
+ r.power_levels = get_admins(stevents);
+
+ rooms.push(r);
+ }
+
+ // left rooms
+ for k in leave.keys() {
+ let r = Self::new(k.clone(), RoomMembership::Left);
+ rooms.push(r);
+ }
+
+ // invitations
+ for (k, room) in invite.iter() {
+ let stevents = &room.invite_state.events;
+ let name = calculate_room_name(stevents, userid);
+
+ if let Some(ev) = stevents
+ .iter()
+ .find(|x| x["membership"] == "invite" && x["state_key"] == userid)
+ {
+ if let Ok((alias, avatar)) =
+ get_user_avatar(baseu, ev["sender"].as_str().unwrap_or_default())
+ {
+ let inv_sender = Member {
+ alias: Some(alias),
+ avatar: Some(avatar),
+ uid: String::from(userid),
+ };
+ let mut r = Self::new(k.clone(), RoomMembership::Invited(inv_sender));
+ r.name = name;
+
+ r.avatar = Some(evc(stevents, "m.room.avatar", "url"));
+ r.alias = Some(evc(stevents, "m.room.canonical_alias", "alias"));
+ r.topic = Some(evc(stevents, "m.room.topic", "topic"));
+ r.direct = direct.contains(k);
+
+ rooms.push(r);
+ }
+ }
+ }
+
+ rooms
+ }
+
pub fn add_receipt_from_json(&mut self, mut events: Vec<&JsonValue>) {
let receipts = events
.pop()
@@ -215,3 +327,117 @@ pub struct PublicRoomsChunk {
pub topic: Option<String>,
pub world_readable: bool,
}
+
+fn evc(events: &Vec<JsonValue>, t: &str, field: &str) -> String {
+ events
+ .iter()
+ .find(|x| x["type"] == t)
+ .and_then(|js| js["content"][field].as_str())
+ .map(Into::into)
+ .unwrap_or_default()
+}
+
+fn get_admins(stevents: &Vec<JsonValue>) -> HashMap<String, i32> {
+ let mut admins = HashMap::new();
+
+ let plevents = stevents
+ .iter()
+ .filter(|x| x["type"] == "m.room.power_levels");
+
+ for ev in plevents {
+ if let Some(users) = ev["content"]["users"].as_object() {
+ for u in users.keys() {
+ let level = users[u].as_i64().unwrap_or_default();
+ admins.insert(u.to_string(), level as i32);
+ }
+ }
+ }
+
+ admins
+}
+
+fn calculate_room_name(events: &Vec<JsonValue>, userid: &str) -> Option<String> {
+ // looking for "m.room.name" event
+ if let Some(name) = events.iter().find(|x| x["type"] == "m.room.name") {
+ if let Some(name) = name["content"]["name"].as_str() {
+ if !name.to_string().is_empty() {
+ return Some(name.to_string());
+ }
+ }
+ }
+
+ // looking for "m.room.canonical_alias" event
+ if let Some(name) = events
+ .iter()
+ .find(|x| x["type"] == "m.room.canonical_alias")
+ {
+ if let Some(name) = name["content"]["alias"].as_str() {
+ return Some(name.to_string());
+ }
+ }
+
+ // we look for members that aren't me
+ let filter = |x: &&JsonValue| {
+ (x["type"] == "m.room.member"
+ && ((x["content"]["membership"] == "join" && x["sender"] != userid)
+ || (x["content"]["membership"] == "invite" && x["state_key"] != userid)))
+ };
+ let c = events.iter().filter(&filter);
+ let members = events.iter().filter(&filter);
+ let mut members2 = events.iter().filter(&filter);
+
+ if c.count() == 0 {
+ // we don't have information to calculate the name
+ return None;
+ }
+
+ let m1 = match members2.next() {
+ Some(m) => {
+ let sender = m["sender"].as_str().unwrap_or("NONAMED");
+ m["content"]["displayname"].as_str().unwrap_or(sender)
+ }
+ None => "",
+ };
+ let m2 = match members2.next() {
+ Some(m) => {
+ let sender = m["sender"].as_str().unwrap_or("NONAMED");
+ m["content"]["displayname"].as_str().unwrap_or(sender)
+ }
+ None => "",
+ };
+
+ let name = match members.count() {
+ 0 => String::from("EMPTY ROOM"),
+ 1 => String::from(m1),
+ 2 => format!("{} and {}", m1, m2),
+ _ => format!("{} and Others", m1),
+ };
+
+ Some(name)
+}
+
+fn parse_room_member(msg: &JsonValue) -> Option<Member> {
+ let sender = msg["sender"].as_str().unwrap_or_default();
+
+ let c = &msg["content"];
+
+ let membership = c["membership"].as_str();
+ if membership.is_none() || membership.unwrap() != "join" {
+ return None;
+ }
+
+ let displayname = match c["displayname"].as_str() {
+ None => None,
+ Some(s) => Some(String::from(s)),
+ };
+ let avatar_url = match c["avatar_url"].as_str() {
+ None => None,
+ Some(s) => Some(String::from(s)),
+ };
+
+ Some(Member {
+ uid: String::from(sender),
+ alias: displayname,
+ avatar: avatar_url,
+ })
+}
diff --git a/fractal-matrix-api/src/model/sync.rs b/fractal-matrix-api/src/model/sync.rs
new file mode 100644
index 00000000..1b740ead
--- /dev/null
+++ b/fractal-matrix-api/src/model/sync.rs
@@ -0,0 +1,125 @@
+use serde::Deserialize;
+use serde_json::Value as JsonValue;
+use std::collections::HashMap;
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct SyncResponse {
+ pub next_batch: String,
+ #[serde(default)]
+ pub rooms: Rooms,
+ pub presence: Option<Presence>,
+ #[serde(default)]
+ pub account_data: AccountData,
+ pub to_device: Option<ToDevice>,
+ pub device_lists: Option<DeviceLists>,
+ #[serde(default)]
+ pub device_one_time_keys_count: HashMap<String, u64>,
+}
+
+#[derive(Clone, Debug, Default, Deserialize)]
+pub struct Rooms {
+ #[serde(default)]
+ pub leave: HashMap<String, LeftRoom>,
+ #[serde(default)]
+ pub join: HashMap<String, JoinedRoom>,
+ #[serde(default)]
+ pub invite: HashMap<String, InvitedRoom>,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct JoinedRoom {
+ #[serde(default)]
+ pub unread_notifications: UnreadNotificationsCount,
+ #[serde(default)]
+ pub timeline: Timeline,
+ #[serde(default)]
+ pub state: State,
+ #[serde(default)]
+ pub account_data: AccountData,
+ #[serde(default)]
+ pub ephemeral: Ephemeral,
+}
+
+#[derive(Clone, Debug, Default, Deserialize)]
+pub struct Ephemeral {
+ // TODO: Implement Event
+ #[serde(default)]
+ pub events: Vec<JsonValue>,
+}
+
+#[derive(Clone, Debug, Default, Deserialize)]
+pub struct UnreadNotificationsCount {
+ #[serde(default)]
+ pub highlight_count: i32,
+ #[serde(default)]
+ pub notification_count: i32,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct InvitedRoom {
+ #[serde(default)]
+ pub invite_state: InviteState,
+}
+
+#[derive(Clone, Debug, Default, Deserialize)]
+pub struct InviteState {
+ // TODO: Implement StrippedState
+ #[serde(default)]
+ pub events: Vec<JsonValue>,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct LeftRoom {
+ #[serde(default)]
+ pub timeline: Timeline,
+ #[serde(default)]
+ pub state: State,
+ #[serde(default)]
+ pub account_data: AccountData,
+}
+
+#[derive(Clone, Debug, Default, Deserialize)]
+pub struct State {
+ // TODO: Implement StateEvent
+ #[serde(default)]
+ pub events: Vec<JsonValue>,
+}
+
+#[derive(Clone, Debug, Default, Deserialize)]
+pub struct Timeline {
+ #[serde(default)]
+ pub limited: bool,
+ pub prev_batch: Option<String>,
+ // TODO: Implement RoomEvent
+ #[serde(default)]
+ pub events: Vec<JsonValue>,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct Presence {
+ // TODO: Implement Event
+ #[serde(default)]
+ pub events: Vec<JsonValue>,
+}
+
+#[derive(Clone, Debug, Default, Deserialize)]
+pub struct AccountData {
+ // TODO: Implement Event
+ #[serde(default)]
+ pub events: Vec<JsonValue>,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct ToDevice {
+ // TODO: Implement Event
+ #[serde(default)]
+ pub events: Vec<JsonValue>,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct DeviceLists {
+ #[serde(default)]
+ pub changed: Vec<String>,
+ #[serde(default)]
+ pub left: Vec<String>,
+}
diff --git a/fractal-matrix-api/src/types.rs b/fractal-matrix-api/src/types.rs
index 3d2e4c23..d841d436 100644
--- a/fractal-matrix-api/src/types.rs
+++ b/fractal-matrix-api/src/types.rs
@@ -14,4 +14,7 @@ pub use crate::model::room::RoomTag;
pub use crate::model::room::ThirdPartyNetworks;
pub use crate::model::stickers::Sticker;
pub use crate::model::stickers::StickerGroup;
+pub use crate::model::sync::JoinedRoom;
+pub use crate::model::sync::SyncResponse;
+pub use crate::model::sync::UnreadNotificationsCount;
pub use crate::model::userinfo::UserInfo;
diff --git a/fractal-matrix-api/src/util.rs b/fractal-matrix-api/src/util.rs
index 9e457161..e91376d9 100644
--- a/fractal-matrix-api/src/util.rs
+++ b/fractal-matrix-api/src/util.rs
@@ -14,17 +14,13 @@ use std::fs::create_dir_all;
use std::fs::File;
use std::io::prelude::*;
-use std::collections::HashSet;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::Duration as StdDuration;
use crate::error::Error;
-use crate::types::Event;
-use crate::types::Member;
use crate::types::Message;
-use crate::types::{Room, RoomMembership, RoomTag};
use reqwest::header::CONTENT_TYPE;
@@ -140,277 +136,24 @@ macro_rules! query {
};
}
-pub fn evc(events: &JsonValue, t: &str, field: &str) -> String {
+pub fn parse_m_direct(events: &Vec<JsonValue>) -> HashMap<String, Vec<String>> {
events
- .as_array()
- .and_then(|arr| arr.iter().find(|x| x["type"] == t))
- .and_then(|js| js["content"][field].as_str())
- .map(Into::into)
- .unwrap_or_default()
-}
-
-pub fn parse_m_direct(r: &JsonValue) -> HashMap<String, Vec<String>> {
- let mut direct = HashMap::new();
-
- &r["account_data"]["events"]
- .as_array()
- .unwrap_or(&vec![])
.iter()
.find(|x| x["type"] == "m.direct")
.and_then(|js| js["content"].as_object())
- .map(|js| {
- for (k, v) in js.iter() {
- let value = v
- .as_array()
- .unwrap_or(&vec![])
- .iter()
- .map(|rid| rid.as_str().unwrap_or_default().to_string())
- .collect::<Vec<String>>();
- direct.insert(k.clone(), value);
- }
- });
-
- direct
-}
-
-pub fn get_rooms_from_json(r: &JsonValue, userid: &str, baseu: &Url) -> Result<Vec<Room>, Error> {
- let rooms = &r["rooms"];
-
- let join = rooms["join"].as_object().ok_or(Error::BackendError)?;
- let leave = rooms["leave"].as_object().ok_or(Error::BackendError)?;
- let invite = rooms["invite"].as_object().ok_or(Error::BackendError)?;
-
- // getting the list of direct rooms
- let mut direct: HashSet<String> = HashSet::new();
- for v in parse_m_direct(r).values() {
- for rid in v {
- direct.insert(rid.clone());
- }
- }
-
- let mut rooms: Vec<Room> = vec![];
- for k in join.keys() {
- let room = join.get(k).ok_or(Error::BackendError)?;
- let stevents = &room["state"]["events"];
- let timeline = &room["timeline"];
- let ephemeral = &room["ephemeral"];
- let dataevs = &room["account_data"]["events"];
- let name = calculate_room_name(stevents, userid)?;
- let mut room_tag = RoomTag::None;
- if let Some(ev) = dataevs.as_array() {
- for tag in ev.iter().filter(|x| x["type"] == "m.tag") {
- if tag["content"]["tags"]["m.favourite"].as_object().is_some() {
- room_tag = RoomTag::Favourite;
- }
- }
- }
-
- let mut r = Room::new(k.clone(), RoomMembership::Joined(room_tag));
- r.name = name;
-
- r.avatar = Some(evc(stevents, "m.room.avatar", "url"));
- r.alias = Some(evc(stevents, "m.room.canonical_alias", "alias"));
- r.topic = Some(evc(stevents, "m.room.topic", "topic"));
- r.direct = direct.contains(k);
- r.notifications = room["unread_notifications"]["notification_count"]
- .as_i64()
- .unwrap_or_default() as i32;
- r.highlight = room["unread_notifications"]["highlight_count"]
- .as_i64()
- .unwrap_or_default() as i32;
-
- r.prev_batch = timeline["prev_batch"].as_str().map(String::from);
-
- if let Some(evs) = timeline["events"].as_array() {
- let ms = Message::from_json_events_iter(&k, evs.iter());
- r.messages.extend(ms);
- }
-
- if let Some(evs) = ephemeral["events"].as_array() {
- r.add_receipt_from_json(
- evs.into_iter()
- .filter(|ev| ev["type"] == "m.receipt")
- .collect::<Vec<&JsonValue>>(),
- );
- }
- // Adding fully read to the receipts events
- if let Some(evs) = dataevs.as_array() {
- if let Some(fread) = evs.into_iter().find(|x| x["type"] == "m.fully_read") {
- if let Some(ev) = fread["content"]["event_id"].as_str() {
- r.add_receipt_from_fully_read(userid, ev);
- }
- }
- }
-
- let mevents = stevents
- .as_array()
- .unwrap()
- .iter()
- .filter(|x| x["type"] == "m.room.member");
-
- for ev in mevents {
- let member = parse_room_member(ev);
- if let Some(m) = member {
- r.members.insert(m.uid.clone(), m.clone());
- }
- }
-
- // power levels info
- r.power_levels = get_admins(stevents);
-
- rooms.push(r);
- }
-
- // left rooms
- for k in leave.keys() {
- let r = Room::new(k.clone(), RoomMembership::Left);
- rooms.push(r);
- }
-
- // invitations
- for k in invite.keys() {
- let room = invite.get(k).ok_or(Error::BackendError)?;
- let stevents = &room["invite_state"]["events"];
- let name = calculate_room_name(stevents, userid)?;
- if let Some(arr) = stevents.as_array() {
- if let Some(ev) = arr
- .iter()
- .find(|x| x["membership"] == "invite" && x["state_key"] == userid)
- {
- if let Ok((alias, avatar)) =
- get_user_avatar(baseu, ev["sender"].as_str().unwrap_or_default())
- {
- let inv_sender = Member {
- alias: Some(alias),
- avatar: Some(avatar),
- uid: String::from(userid),
- };
- let mut r = Room::new(k.clone(), RoomMembership::Invited(inv_sender));
- r.name = name;
-
- r.avatar = Some(evc(stevents, "m.room.avatar", "url"));
- r.alias = Some(evc(stevents, "m.room.canonical_alias", "alias"));
- r.topic = Some(evc(stevents, "m.room.topic", "topic"));
- r.direct = direct.contains(k);
-
- rooms.push(r);
- }
- }
- }
- }
-
- Ok(rooms)
-}
-
-pub fn get_admins(stevents: &JsonValue) -> HashMap<String, i32> {
- let mut admins = HashMap::new();
-
- let plevents = stevents
- .as_array()
- .unwrap()
+ .cloned()
+ .unwrap_or_default()
.iter()
- .filter(|x| x["type"] == "m.room.power_levels");
-
- for ev in plevents {
- if let Some(users) = ev["content"]["users"].as_object() {
- for u in users.keys() {
- let level = users[u].as_i64().unwrap_or_default();
- admins.insert(u.to_string(), level as i32);
- }
- }
- }
-
- admins
-}
-
-pub fn get_rooms_timeline_from_json(
- baseu: &Url,
- r: &JsonValue,
- tk: &str,
- prev_batch: &str,
-) -> Result<Vec<Message>, Error> {
- let rooms = &r["rooms"];
- let join = rooms["join"].as_object().ok_or(Error::BackendError)?;
-
- let mut msgs: Vec<Message> = vec![];
- for k in join.keys() {
- let room = join.get(k).ok_or(Error::BackendError)?;
-
- if let (Some(true), Some(pb)) = (
- room["timeline"]["limited"].as_bool(),
- room["timeline"]["prev_batch"].as_str(),
- ) {
- let pbs = pb.to_string();
- let fill_the_gap =
- fill_room_gap(baseu, String::from(tk), k.clone(), &prev_batch, &pbs)?;
- for m in fill_the_gap {
- msgs.push(m);
- }
- }
-
- let timeline = room["timeline"]["events"].as_array();
- if timeline.is_none() {
- continue;
- }
-
- let events = timeline.unwrap().iter();
- let ms = Message::from_json_events_iter(&k, events);
- msgs.extend(ms);
- }
-
- Ok(msgs)
-}
-
-pub fn get_rooms_notifies_from_json(r: &JsonValue) -> Result<Vec<(String, i32, i32)>, Error> {
- let rooms = &r["rooms"];
- let join = rooms["join"].as_object().ok_or(Error::BackendError)?;
-
- let mut out: Vec<(String, i32, i32)> = vec![];
- for k in join.keys() {
- let room = join.get(k).ok_or(Error::BackendError)?;
- let n = room["unread_notifications"]["notification_count"]
- .as_i64()
- .unwrap_or_default() as i32;
- let h = room["unread_notifications"]["highlight_count"]
- .as_i64()
- .unwrap_or_default() as i32;
-
- out.push((k.clone(), n, h));
- }
-
- Ok(out)
-}
-
-pub fn parse_sync_events(r: &JsonValue) -> Result<Vec<Event>, Error> {
- let rooms = &r["rooms"];
- let join = rooms["join"].as_object().ok_or(Error::BackendError)?;
-
- let mut evs: Vec<Event> = vec![];
- for k in join.keys() {
- let room = join.get(k).ok_or(Error::BackendError)?;
- let timeline = room["timeline"]["events"].as_array();
- if timeline.is_none() {
- return Ok(evs);
- }
-
- let events = timeline
- .unwrap()
- .iter()
- .filter(|x| x["type"] != "m.room.message");
-
- for ev in events {
- //info!("ev: {:#?}", ev);
- evs.push(Event {
- room: k.clone(),
- sender: String::from(ev["sender"].as_str().unwrap_or_default()),
- content: ev["content"].clone(),
- stype: String::from(ev["type"].as_str().unwrap_or_default()),
- id: String::from(ev["id"].as_str().unwrap_or_default()),
- });
- }
- }
-
- Ok(evs)
+ .map(|(k, v)| {
+ let value = v
+ .as_array()
+ .unwrap_or(&vec![])
+ .iter()
+ .map(|rid| rid.as_str().map(Into::into).unwrap_or_default())
+ .collect();
+ (k.clone(), value)
+ })
+ .collect()
}
pub fn get_prev_batch_from(
@@ -718,111 +461,6 @@ pub fn get_room_avatar(base: &Url, tk: &str, userid: &str, roomid: &str) -> Resu
Ok(fname)
}
-pub fn calculate_room_name(roomst: &JsonValue, userid: &str) -> Result<Option<String>, Error> {
- // looking for "m.room.name" event
- let events = roomst.as_array().ok_or(Error::BackendError)?;
- if let Some(name) = events.iter().find(|x| x["type"] == "m.room.name") {
- if let Some(name) = name["content"]["name"].as_str() {
- if !name.to_string().is_empty() {
- return Ok(Some(name.to_string()));
- }
- }
- }
-
- // looking for "m.room.canonical_alias" event
- if let Some(name) = events
- .iter()
- .find(|x| x["type"] == "m.room.canonical_alias")
- {
- if let Some(name) = name["content"]["alias"].as_str() {
- return Ok(Some(name.to_string()));
- }
- }
-
- // we look for members that aren't me
- let filter = |x: &&JsonValue| {
- (x["type"] == "m.room.member"
- && ((x["content"]["membership"] == "join" && x["sender"] != userid)
- || (x["content"]["membership"] == "invite" && x["state_key"] != userid)))
- };
- let c = events.iter().filter(&filter);
- let members = events.iter().filter(&filter);
- let mut members2 = events.iter().filter(&filter);
-
- if c.count() == 0 {
- // we don't have information to calculate the name
- return Ok(None);
- }
-
- let m1 = match members2.next() {
- Some(m) => {
- let sender = m["sender"].as_str().unwrap_or("NONAMED");
- m["content"]["displayname"].as_str().unwrap_or(sender)
- }
- None => "",
- };
- let m2 = match members2.next() {
- Some(m) => {
- let sender = m["sender"].as_str().unwrap_or("NONAMED");
- m["content"]["displayname"].as_str().unwrap_or(sender)
- }
- None => "",
- };
-
- let name = match members.count() {
- 0 => String::from("EMPTY ROOM"),
- 1 => String::from(m1),
- 2 => format!("{} and {}", m1, m2),
- _ => format!("{} and Others", m1),
- };
-
- Ok(Some(name))
-}
-
-/// Recursive function that tries to get all messages in a room from a batch id to a batch id,
-/// following the response pagination
-pub fn fill_room_gap(
- baseu: &Url,
- tk: String,
- roomid: String,
- from: &str,
- to: &str,
-) -> Result<Vec<Message>, Error> {
- let mut ms: Vec<Message> = vec![];
- let nend;
-
- let params = &[
- ("dir", String::from("f")),
- ("limit", format!("{}", globals::PAGE_LIMIT)),
- ("access_token", tk.clone()),
- ("from", String::from(from)),
- ("to", String::from(to)),
- ];
-
- let path = format!("rooms/{}/messages", roomid);
- let url = client_url(baseu, &path, params)?;
-
- let r = json_q("get", &url, &json!(null), globals::TIMEOUT)?;
- nend = String::from(r["end"].as_str().unwrap_or_default());
-
- let array = r["chunk"].as_array();
- if array.is_none() || array.unwrap().is_empty() {
- return Ok(ms);
- }
-
- let evs = array.unwrap().iter();
- let mevents = Message::from_json_events_iter(&roomid, evs);
- ms.extend(mevents);
-
- // loading more until no more messages
- let more = fill_room_gap(baseu, tk, roomid, &nend, to)?;
- for m in more.iter() {
- ms.insert(0, m.clone());
- }
-
- Ok(ms)
-}
-
pub fn build_url(base: &Url, path: &str, params: &[(&str, String)]) -> Result<Url, Error> {
let mut url = base.join(path)?;
@@ -882,32 +520,6 @@ pub fn get_user_avatar_img(baseu: &Url, userid: &str, avatar: &str) -> Result<St
thumb(baseu, &avatar, Some(&dest))
}
-pub fn parse_room_member(msg: &JsonValue) -> Option<Member> {
- let sender = msg["sender"].as_str().unwrap_or_default();
-
- let c = &msg["content"];
-
- let membership = c["membership"].as_str();
- if membership.is_none() || membership.unwrap() != "join" {
- return None;
- }
-
- let displayname = match c["displayname"].as_str() {
- None => None,
- Some(s) => Some(String::from(s)),
- };
- let avatar_url = match c["avatar_url"].as_str() {
- None => None,
- Some(s) => Some(String::from(s)),
- };
-
- Some(Member {
- uid: String::from(sender),
- alias: displayname,
- avatar: avatar_url,
- })
-}
-
pub fn encode_uid(userid: &str) -> String {
utf8_percent_encode(userid, USERINFO_ENCODE_SET).collect::<String>()
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]