From f46c1638bbb0d5218ee0479243a6f54c4a78041b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Moreno?= Date: Tue, 29 Aug 2017 12:09:39 +0200 Subject: [PATCH] Unread messages in the room list --- res/main_window.glade | 13 +++++++++ src/app.rs | 63 ++++++++++++++++++++++++++++++++++--------- src/backend.rs | 54 ++++++++++++++++++++++++++----------- src/types.rs | 40 ++++++++++++++++++++++++--- src/util.rs | 33 ++++++++++++++++++----- 5 files changed, 166 insertions(+), 37 deletions(-) diff --git a/res/main_window.glade b/res/main_window.glade index dc18840..7c5f281 100644 --- a/res/main_window.glade +++ b/res/main_window.glade @@ -24,6 +24,8 @@ + + @@ -71,6 +73,17 @@ + + + + + + + 2 + + + + diff --git a/src/app.rs b/src/app.rs index b813ab3..69955c9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -298,31 +298,37 @@ impl AppOp { self.backend.send(BKCommand::Sync).unwrap(); } - pub fn set_rooms(&mut self, rooms: HashMap, def: Option<(String, String)>) { + pub fn set_rooms(&mut self, rooms: Vec, def: Option) { let store: gtk::TreeStore = self.gtk_builder.get_object("rooms_tree_store") .expect("Couldn't find rooms_tree_store in ui file."); - let mut array: Vec<(String, String)> = vec![]; - for (id, name) in rooms { - array.push((name, id)); + let mut array: Vec = vec![]; + + for r in rooms { + array.push(r); } - array.sort_by(|x, y| x.0.to_lowercase().cmp(&y.0.to_lowercase())); + array.sort_by(|x, y| x.name.to_lowercase().cmp(&y.name.to_lowercase())); - let mut default: Option<(String, String)> = def; + let mut default: Option = def; for v in array { if default.is_none() { - default = Some((v.0.clone(), v.1.clone())); + default = Some(v.clone()); } + let ns = match v.notifications { + 0 => String::new(), + i => format!("{}", i), + }; + store.insert_with_values(None, None, - &[0, 1], - &[&v.0, &v.1]); + &[0, 1, 2], + &[&v.name, &v.id, &ns]); } if let Some(def) = default { - self.set_active_room(def.1, def.0); + self.set_active_room(def.id, def.name); } else { self.room_panel(RoomPanel::NoRoom); } @@ -434,9 +440,38 @@ impl AppOp { MsgPos::Bottom => messages.add(&msg), MsgPos::Top => messages.insert(&msg, 1), }; - } else { - // TODO: update the unread messages count in room list + self.update_room_notifications(&msg.room, |n| n + 1); + } + } + + pub fn update_room_notifications(&self, roomid: &str, f: fn(i32) -> i32) { + let store: gtk::TreeStore = self.gtk_builder.get_object("rooms_tree_store") + .expect("Couldn't find rooms_tree_store in ui file."); + + if let Some(iter) = store.get_iter_first() { + loop { + let v1 = store.get_value(&iter, 1); + let id: &str = v1.get().unwrap(); + let v2 = store.get_value(&iter, 2); + let ns: &str = v2.get().unwrap(); + let res: Result = ns.parse(); + let n: i32 = f(res.unwrap_or(0)); + let formatted = match n { + 0 => String::from(""), + i => format!("{}", i), + }; + if id == roomid { + store.set_value(&iter, 2, >k::Value::from(&formatted)); + } + if !store.iter_next(&iter) { break; } + } + } + } + + pub fn mark_as_read(&self, msgs: Vec) { + if let Some(msg) = msgs.iter().filter(|x| x.room == self.active_room).last() { + self.backend.send(BKCommand::MarkAsRead(msg.room.clone(), msg.id.clone())).unwrap(); } } @@ -641,6 +676,7 @@ impl App { if !msgs.is_empty() { theop.lock().unwrap().scroll_down(); + theop.lock().unwrap().mark_as_read(msgs); } theop.lock().unwrap().room_panel(RoomPanel::Room); @@ -671,6 +707,9 @@ impl App { Ok(BKResponse::JoinRoom) => { theop.lock().unwrap().reload_rooms(); }, + Ok(BKResponse::MarkedAsRead(r, _)) => { + theop.lock().unwrap().update_room_notifications(&r, |_| 0); + }, // errors Ok(err) => { println!("Query error: {:?}", err); diff --git a/src/backend.rs b/src/backend.rs index aebce22..55fa62a 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -11,7 +11,6 @@ use self::serde_json::Value as JsonValue; use std::sync::{Arc, Mutex}; use std::thread; -use std::collections::HashMap; use self::url::Url; use std::sync::mpsc::{Sender, Receiver}; use std::sync::mpsc::channel; @@ -62,6 +61,7 @@ pub enum BKCommand { DirectoryProtocols, DirectorySearch(String, String, bool), JoinRoom(String), + MarkAsRead(String, String), } #[derive(Debug)] @@ -70,7 +70,7 @@ pub enum BKResponse { Name(String), Avatar(String), Sync, - Rooms(HashMap, Option<(String, String)>), + Rooms(Vec, Option), RoomDetail(String, String), RoomAvatar(String), RoomMessages(Vec), @@ -80,6 +80,7 @@ pub enum BKResponse { DirectoryProtocols(Vec), DirectorySearch(Vec), JoinRoom, + MarkedAsRead(String, String), //errors UserNameError(Error), @@ -96,6 +97,7 @@ pub enum BKResponse { CommandError(Error), DirectoryError(Error), JoinRoomError(Error), + MarkAsReadError(Error), } @@ -194,6 +196,10 @@ impl Backend { let r = self.join_room(roomid); bkerror!(r, tx, BKResponse::JoinRoomError); }, + Ok(BKCommand::MarkAsRead(roomid, evid)) => { + let r = self.mark_as_read(roomid, evid); + bkerror!(r, tx, BKResponse::MarkAsReadError); + }, Ok(BKCommand::ShutDown) => { return false; }, @@ -390,11 +396,11 @@ impl Backend { if since.is_empty() { let rooms = get_rooms_from_json(r, &userid).unwrap(); - let mut def: Option<(String, String)> = None; + let mut def: Option = None; let jtr = data.lock().unwrap().join_to_room.clone(); if !jtr.is_empty() { - if let Some(name) = rooms.iter().find(|x| *(x.0) == jtr) { - def = Some((name.1.clone(), name.0.clone())); + if let Some(r) = rooms.iter().find(|x| x.id == jtr) { + def = Some(r.clone()); } } @@ -487,7 +493,6 @@ impl Backend { } url = url.join(¶ms)?; - let tx = self.tx.clone(); let data = self.data.clone(); get!(&url, @@ -688,16 +693,15 @@ impl Backend { let mut rooms: Vec = vec![]; for room in r["chunk"].as_array().unwrap() { let alias = String::from(room["canonical_alias"].as_str().unwrap_or("")); - let r = Room { - alias: alias, - id: String::from(room["room_id"].as_str().unwrap_or("")), - avatar: String::from(room["avatar_url"].as_str().unwrap_or("")), - name: String::from(room["name"].as_str().unwrap_or("")), - topic: String::from(room["topic"].as_str().unwrap_or("")), - members: room["num_joined_members"].as_i64().unwrap_or(0) as i32, - world_readable: room["world_readable"].as_bool().unwrap_or(false), - guest_can_join: room["guest_can_join"].as_bool().unwrap_or(false), - }; + let id = String::from(room["room_id"].as_str().unwrap_or("")); + let name = String::from(room["name"].as_str().unwrap_or("")); + let mut r = Room::new(id, name); + r.alias = alias; + r.avatar = String::from(room["avatar_url"].as_str().unwrap_or("")); + r.topic = String::from(room["topic"].as_str().unwrap_or("")); + r.members = room["num_joined_members"].as_i64().unwrap_or(0) as i32; + r.world_readable = room["world_readable"].as_bool().unwrap_or(false); + r.guest_can_join = room["guest_can_join"].as_bool().unwrap_or(false); rooms.push(r); } @@ -727,4 +731,22 @@ impl Backend { Ok(()) } + + pub fn mark_as_read(&self, roomid: String, eventid: String) -> Result<(), Error> { + let baseu = self.get_base_url()?; + let tk = self.data.lock().unwrap().access_token.clone(); + let mut url = baseu.join("/_matrix/client/r0/rooms/")?; + url = url.join(&format!("{}/receipt/m.read/{}", roomid, eventid))?; + url = url.join(&format!("?access_token={}", tk))?; + + let tx = self.tx.clone(); + let r = roomid.clone(); + let e = eventid.clone(); + post!(&url, + move |_: JsonValue| { tx.send(BKResponse::MarkedAsRead(r, e)).unwrap(); }, + |err| { tx.send(BKResponse::MarkAsReadError(err)).unwrap(); } + ); + + Ok(()) + } } diff --git a/src/types.rs b/src/types.rs index d372619..aaaa0c3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -11,6 +11,7 @@ pub struct Message { pub room: String, pub thumb: String, pub url: String, + pub id: String, } #[derive(Debug)] @@ -40,9 +41,42 @@ pub struct Room { pub id: String, pub avatar: String, pub name: String, - pub guest_can_join: bool, pub topic: String, - pub members: i32, - pub world_readable: bool, pub alias: String, + pub guest_can_join: bool, + pub world_readable: bool, + pub members: i32, + pub notifications: i32, +} + +impl Room { + pub fn new(id: String, name: String) -> Room { + Room { + id: id, + name: name, + avatar: String::new(), + topic: String::new(), + alias: String::new(), + guest_can_join: true, + world_readable: true, + members: 0, + notifications: 0, + } + } +} + +impl Clone for Room { + fn clone(&self) -> Room { + Room { + id: self.id.clone(), + name: self.name.clone(), + avatar: self.avatar.clone(), + topic: self.topic.clone(), + alias: self.alias.clone(), + guest_can_join: self.guest_can_join, + world_readable: self.world_readable, + members: self.members, + notifications: self.notifications, + } + } } diff --git a/src/util.rs b/src/util.rs index 96419f2..1698655 100644 --- a/src/util.rs +++ b/src/util.rs @@ -11,7 +11,6 @@ use self::regex::Regex; use self::serde_json::Value as JsonValue; -use std::collections::HashMap; use self::url::Url; use std::io::Read; use std::path::Path; @@ -27,6 +26,7 @@ use self::time::Duration; use error::Error; use types::Message; +use types::Room; // from https://stackoverflow.com/a/43992218/1592377 @@ -134,7 +134,18 @@ macro_rules! thumb { }; } -pub fn get_rooms_from_json(r: JsonValue, userid: &str) -> Result, Error> { +pub fn evc(events: &JsonValue, t: &str, field: &str) -> String { + if let Some(arr) = events.as_array() { + return match arr.iter().find(|x| x["type"] == t) { + Some(js) => String::from(js["content"][field].as_str().unwrap_or("")), + None => String::new(), + }; + } + + String::new() +} + +pub fn get_rooms_from_json(r: JsonValue, userid: &str) -> Result, Error> { let rooms = &r["rooms"]; // TODO: do something with invite and leave //let invite = rooms["invite"].as_object().ok_or(Error::BackendError)?; @@ -142,14 +153,22 @@ pub fn get_rooms_from_json(r: JsonValue, userid: &str) -> Result = HashMap::new(); + let mut rooms: Vec = vec![]; for k in join.keys() { let room = join.get(k).ok_or(Error::BackendError)?; - let name = calculate_room_name(&room["state"]["events"], userid)?; - rooms_map.insert(k.clone(), name); + let stevents = &room["state"]["events"]; + let name = calculate_room_name(stevents, userid)?; + let mut r = Room::new(k.clone(), name); + + r.avatar = evc(stevents, "m.room.avatar", "url"); + r.alias = evc(stevents, "m.room.canonical_alias", "alias"); + r.topic = evc(stevents, "m.room.topic", "topic"); + r.notifications = room["unread_notifications"]["notification_count"] + .as_i64().unwrap_or(0) as i32; + rooms.push(r); } - Ok(rooms_map) + Ok(rooms) } pub fn get_rooms_timeline_from_json(baseu: &Url, r: JsonValue) -> Result, Error> { @@ -417,6 +436,7 @@ pub fn calculate_room_name(roomst: &JsonValue, userid: &str) -> Result Message { let sender = msg["sender"].as_str().unwrap_or(""); let age = msg["age"].as_i64().unwrap_or(0); + let id = msg["id"].as_str().unwrap_or(""); let c = &msg["content"]; let mtype = c["msgtype"].as_str().unwrap_or(""); @@ -442,6 +462,7 @@ pub fn parse_room_message(baseu: &Url, roomid: String, msg: &JsonValue) -> Messa room: roomid.clone(), url: url, thumb: thumb, + id: String::from(id), } }