diff --git a/fractal-api/src/backend.rs b/fractal-api/src/backend.rs deleted file mode 100644 index 15a4af59..00000000 --- a/fractal-api/src/backend.rs +++ /dev/null @@ -1,1214 +0,0 @@ -extern crate url; -extern crate serde_json; -extern crate tree_magic; -extern crate chrono; - -use self::chrono::prelude::*; - -use self::serde_json::Value as JsonValue; - -use std::path::Path; -use std::sync::{Arc, Mutex}; -use std::thread; -use self::url::Url; -use std::sync::mpsc::{Sender, Receiver}; -use std::sync::mpsc::channel; -use std::sync::mpsc::RecvError; - -use util::*; -use globals; -use error::Error; - -use types::Message; -use types::Member; -use types::Protocol; -use types::Room; -use types::Event; - -use std::fs::File; -use std::io::prelude::*; - -use cache::CacheMap; - - -pub struct BackendData { - user_id: String, - access_token: String, - server_url: String, - since: String, - msgid: i32, - rooms_since: String, - join_to_room: String, -} - -pub struct Backend { - tx: Sender, - data: Arc>, - internal_tx: Option>, - - // user info cache, uid -> (name, avatar) - user_info_cache: CacheMap>>, -} - -#[derive(Debug)] -pub enum BKCommand { - Login(String, String, String), - Logout, - #[allow(dead_code)] - Register(String, String, String), - #[allow(dead_code)] - Guest(String), - GetUsername, - GetAvatar, - Sync, - SyncForced, - GetRoomMessages(String), - GetMessageContext(Message), - GetRoomAvatar(String), - GetThumbAsync(String, Sender), - GetMedia(String), - GetUserInfoAsync(String, Sender<(String, String)>), - SendMsg(Message), - SetRoom(Room), - ShutDown, - DirectoryProtocols, - DirectorySearch(String, String, bool), - JoinRoom(String), - MarkAsRead(String, String), - LeaveRoom(String), - SetRoomName(String, String), - SetRoomTopic(String, String), - SetRoomAvatar(String, String), - AttachFile(String, String), - AttachImage(String, Vec), - Search(String, Option), - NotifyClicked(Message), - NewRoom(String, RoomType), -} - -#[derive(Debug)] -pub enum BKResponse { - Token(String, String), - Logout, - Name(String), - Avatar(String), - Sync(String), - Rooms(Vec, Option), - RoomDetail(String, String, String), - RoomAvatar(String, String), - NewRoomAvatar(String), - RoomMemberEvent(Event), - RoomMessages(Vec), - RoomMessagesInit(Vec), - RoomMessagesTo(Vec), - RoomMembers(Vec), - SendMsg, - DirectoryProtocols(Vec), - DirectorySearch(Vec), - JoinRoom, - LeaveRoom, - MarkedAsRead(String, String), - SetRoomName, - SetRoomTopic, - SetRoomAvatar, - RoomName(String, String), - RoomTopic(String, String), - Media(String), - AttachedFile(Message), - SearchEnd, - NotificationClicked(Message), - NewRoom(Room), - - //errors - UserNameError(Error), - AvatarError(Error), - LoginError(Error), - LogoutError(Error), - GuestLoginError(Error), - SyncError(Error), - RoomDetailError(Error), - RoomAvatarError(Error), - RoomMessagesError(Error), - RoomMembersError(Error), - SendMsgError(Error), - SetRoomError(Error), - CommandError(Error), - DirectoryError(Error), - JoinRoomError(Error), - MarkAsReadError(Error), - LeaveRoomError(Error), - SetRoomNameError(Error), - SetRoomTopicError(Error), - SetRoomAvatarError(Error), - GetRoomAvatarError(Error), - MediaError(Error), - AttachFileError(Error), - SearchError(Error), - NewRoomError(Error), -} - -#[derive(Debug)] -pub enum RoomType { - Public, - Private, -} - - -impl Backend { - pub fn new(tx: Sender) -> Backend { - let data = BackendData { - user_id: String::from("Guest"), - access_token: String::from(""), - server_url: String::from("https://matrix.org"), - since: String::from(""), - msgid: 1, - rooms_since: String::from(""), - join_to_room: String::from(""), - }; - Backend { - tx: tx, - internal_tx: None, - data: Arc::new(Mutex::new(data)), - user_info_cache: CacheMap::new().timeout(60*60), - } - } - - fn get_base_url(&self) -> Result { - let s = self.data.lock().unwrap().server_url.clone(); - let url = Url::parse(&s)?; - Ok(url) - } - - fn url(&self, path: &str, params: Vec<(&str, String)>) -> Result { - let base = self.get_base_url()?; - let tk = self.data.lock().unwrap().access_token.clone(); - - let mut params2 = params.to_vec(); - params2.push(("access_token", tk.clone())); - - client_url!(&base, path, params2) - } - - pub fn command_recv(&mut self, cmd: Result) -> bool { - let tx = self.tx.clone(); - - match cmd { - Ok(BKCommand::Login(user, passwd, server)) => { - let r = self.login(user, passwd, server); - bkerror!(r, tx, BKResponse::LoginError); - } - Ok(BKCommand::Logout) => { - let r = self.logout(); - bkerror!(r, tx, BKResponse::LogoutError); - } - Ok(BKCommand::Register(user, passwd, server)) => { - let r = self.register(user, passwd, server); - bkerror!(r, tx, BKResponse::LoginError); - } - Ok(BKCommand::Guest(server)) => { - let r = self.guest(server); - bkerror!(r, tx, BKResponse::GuestLoginError); - } - Ok(BKCommand::GetUsername) => { - let r = self.get_username(); - bkerror!(r, tx, BKResponse::UserNameError); - } - Ok(BKCommand::GetAvatar) => { - let r = self.get_avatar(); - bkerror!(r, tx, BKResponse::AvatarError); - } - Ok(BKCommand::Sync) => { - let r = self.sync(); - bkerror!(r, tx, BKResponse::SyncError); - } - Ok(BKCommand::SyncForced) => { - self.data.lock().unwrap().since = String::from(""); - let r = self.sync(); - bkerror!(r, tx, BKResponse::SyncError); - } - Ok(BKCommand::GetRoomMessages(room)) => { - let r = self.get_room_messages(room); - bkerror!(r, tx, BKResponse::RoomMessagesError); - } - Ok(BKCommand::GetMessageContext(message)) => { - let r = self.get_message_context(message); - bkerror!(r, tx, BKResponse::RoomMessagesError); - } - Ok(BKCommand::GetUserInfoAsync(sender, ctx)) => { - let r = self.get_user_info_async(&sender, ctx); - bkerror!(r, tx, BKResponse::CommandError); - } - Ok(BKCommand::GetThumbAsync(media, ctx)) => { - let r = self.get_thumb_async(media, ctx); - bkerror!(r, tx, BKResponse::CommandError); - } - Ok(BKCommand::GetMedia(media)) => { - let r = self.get_media(media); - bkerror!(r, tx, BKResponse::CommandError); - } - Ok(BKCommand::SendMsg(msg)) => { - let r = self.send_msg(msg); - bkerror!(r, tx, BKResponse::SendMsgError); - } - Ok(BKCommand::SetRoom(room)) => { - let r = self.set_room(room); - bkerror!(r, tx, BKResponse::SetRoomError); - } - Ok(BKCommand::GetRoomAvatar(room)) => { - let r = self.get_room_avatar(room); - bkerror!(r, tx, BKResponse::GetRoomAvatarError); - } - Ok(BKCommand::DirectoryProtocols) => { - let r = self.protocols(); - bkerror!(r, tx, BKResponse::DirectoryError); - } - Ok(BKCommand::DirectorySearch(dq, dtp, more)) => { - let q = match dq { - ref a if a.is_empty() => None, - b => Some(b), - }; - - let tp = match dtp { - ref a if a.is_empty() => None, - b => Some(b), - }; - - let r = self.room_search(q, tp, more); - bkerror!(r, tx, BKResponse::DirectoryError); - } - Ok(BKCommand::JoinRoom(roomid)) => { - let r = self.join_room(roomid); - bkerror!(r, tx, BKResponse::JoinRoomError); - } - Ok(BKCommand::LeaveRoom(roomid)) => { - let r = self.leave_room(roomid); - bkerror!(r, tx, BKResponse::LeaveRoomError); - } - Ok(BKCommand::MarkAsRead(roomid, evid)) => { - let r = self.mark_as_read(roomid, evid); - bkerror!(r, tx, BKResponse::MarkAsReadError); - } - Ok(BKCommand::SetRoomName(roomid, name)) => { - let r = self.set_room_name(roomid, name); - bkerror!(r, tx, BKResponse::SetRoomNameError); - } - Ok(BKCommand::SetRoomTopic(roomid, topic)) => { - let r = self.set_room_topic(roomid, topic); - bkerror!(r, tx, BKResponse::SetRoomTopicError); - } - Ok(BKCommand::SetRoomAvatar(roomid, fname)) => { - let r = self.set_room_avatar(roomid, fname); - bkerror!(r, tx, BKResponse::SetRoomAvatarError); - } - Ok(BKCommand::AttachFile(roomid, fname)) => { - let r = self.attach_file(roomid, fname); - bkerror!(r, tx, BKResponse::AttachFileError); - } - Ok(BKCommand::AttachImage(roomid, image)) => { - let r = self.attach_image(roomid, image); - bkerror!(r, tx, BKResponse::AttachFileError); - } - Ok(BKCommand::Search(roomid, term)) => { - let r = self.search(roomid, term); - bkerror!(r, tx, BKResponse::SearchError); - } - Ok(BKCommand::NotifyClicked(message)) => { - tx.send(BKResponse::NotificationClicked(message)).unwrap(); - } - Ok(BKCommand::NewRoom(name, privacy)) => { - let r = self.new_room(name, privacy); - bkerror!(r, tx, BKResponse::NewRoomError); - } - Ok(BKCommand::ShutDown) => { - return false; - } - Err(_) => { - return false; - } - }; - - true - } - - pub fn run(mut self) -> Sender { - let (apptx, rx): (Sender, Receiver) = channel(); - - self.internal_tx = Some(apptx.clone()); - thread::spawn(move || loop { - let cmd = rx.recv(); - if !self.command_recv(cmd) { - break; - } - }); - - apptx - } - - pub fn set_room(&self, room: Room) -> Result<(), Error> { - self.get_room_detail(room.id.clone(), String::from("m.room.topic"))?; - self.get_room_avatar(room.id.clone())?; - self.get_room_members(room.id.clone())?; - - Ok(()) - } - - pub fn guest(&self, server: String) -> Result<(), Error> { - let s = server.clone(); - let url = Url::parse(&s).unwrap().join("/_matrix/client/r0/register?kind=guest")?; - self.data.lock().unwrap().server_url = s; - - let data = self.data.clone(); - let tx = self.tx.clone(); - let attrs = json!({}); - post!(&url, &attrs, - |r: JsonValue| { - let uid = String::from(r["user_id"].as_str().unwrap_or("")); - let tk = String::from(r["access_token"].as_str().unwrap_or("")); - data.lock().unwrap().user_id = uid.clone(); - data.lock().unwrap().access_token = tk.clone(); - data.lock().unwrap().since = String::from(""); - tx.send(BKResponse::Token(uid, tk)).unwrap(); - tx.send(BKResponse::Rooms(vec![], None)).unwrap(); - }, - |err| tx.send(BKResponse::GuestLoginError(err)).unwrap()); - - Ok(()) - } - - pub fn login(&self, user: String, password: String, server: String) -> Result<(), Error> { - let s = server.clone(); - self.data.lock().unwrap().server_url = s; - let url = self.url("login", vec![])?; - - let attrs = json!({ - "type": "m.login.password", - "user": user, - "password": password - }); - - let data = self.data.clone(); - - let tx = self.tx.clone(); - post!(&url, &attrs, - |r: JsonValue| { - let uid = String::from(r["user_id"].as_str().unwrap_or("")); - let tk = String::from(r["access_token"].as_str().unwrap_or("")); - - if uid.is_empty() || tk.is_empty() { - tx.send(BKResponse::LoginError(Error::BackendError)).unwrap(); - } else { - data.lock().unwrap().user_id = uid.clone(); - data.lock().unwrap().access_token = tk.clone(); - data.lock().unwrap().since = String::new(); - tx.send(BKResponse::Token(uid, tk)).unwrap(); - } - }, - |err| { tx.send(BKResponse::LoginError(err)).unwrap() } - ); - - Ok(()) - } - - pub fn logout(&self) -> Result<(), Error> { - let url = self.url("logout", vec![])?; - let attrs = json!({}); - - let data = self.data.clone(); - let tx = self.tx.clone(); - post!(&url, &attrs, - |_| { - data.lock().unwrap().user_id = String::new(); - data.lock().unwrap().access_token = String::new(); - data.lock().unwrap().since = String::new(); - tx.send(BKResponse::Logout).unwrap(); - }, - |err| { tx.send(BKResponse::LogoutError(err)).unwrap() } - ); - Ok(()) - } - - pub fn register(&self, user: String, password: String, server: String) -> Result<(), Error> { - let s = server.clone(); - self.data.lock().unwrap().server_url = s; - let url = self.url("register", vec![("kind", strn!("user"))])?; - - let attrs = json!({ - "auth": {"type": "m.login.password"}, - "username": user, - "bind_email": false, - "password": password - }); - - let data = self.data.clone(); - let tx = self.tx.clone(); - post!(&url, &attrs, - |r: JsonValue| { - println!("RESPONSE: {:#?}", r); - let uid = String::from(r["user_id"].as_str().unwrap_or("")); - let tk = String::from(r["access_token"].as_str().unwrap_or("")); - - data.lock().unwrap().user_id = uid.clone(); - data.lock().unwrap().access_token = tk.clone(); - data.lock().unwrap().since = String::from(""); - tx.send(BKResponse::Token(uid, tk)).unwrap(); - }, - |err| { tx.send(BKResponse::LoginError(err)).unwrap() } - ); - - Ok(()) - } - - pub fn get_username(&self) -> Result<(), Error> { - let id = self.data.lock().unwrap().user_id.clone(); - let url = self.url(&format!("profile/{}/displayname", id.clone()), vec![])?; - let tx = self.tx.clone(); - get!(&url, - |r: JsonValue| { - let name = String::from(r["displayname"].as_str().unwrap_or(&id)); - tx.send(BKResponse::Name(name)).unwrap(); - }, - |err| { tx.send(BKResponse::UserNameError(err)).unwrap() } - ); - - Ok(()) - } - - pub fn get_avatar(&self) -> Result<(), Error> { - let baseu = self.get_base_url()?; - let userid = self.data.lock().unwrap().user_id.clone(); - - let tx = self.tx.clone(); - thread::spawn(move || match get_user_avatar(&baseu, &userid) { - Ok((_, fname)) => { - tx.send(BKResponse::Avatar(fname)).unwrap(); - } - Err(err) => { - tx.send(BKResponse::AvatarError(err)).unwrap(); - } - }); - - Ok(()) - } - - pub fn sync(&self) -> Result<(), Error> { - let tk = self.data.lock().unwrap().access_token.clone(); - if tk.is_empty() { - return Err(Error::BackendError); - } - - let since = self.data.lock().unwrap().since.clone(); - let userid = self.data.lock().unwrap().user_id.clone(); - - let mut params: Vec<(&str, String)> = vec![]; - let timeout = 120; - - params.push(("full_state", strn!("false"))); - params.push(("timeout", strn!("30000"))); - - if since.is_empty() { - let filter = format!("{{ - \"room\": {{ - \"state\": {{ - \"types\": [\"m.room.*\"], - }}, - \"timeline\": {{ - \"types\": [\"m.room.message\"], - \"limit\": {}, - }}, - \"ephemeral\": {{ \"types\": [] }} - }}, - \"presence\": {{ \"types\": [] }}, - \"event_format\": \"client\", - \"event_fields\": [\"type\", \"content\", \"sender\", \"event_id\", \"age\", \"unsigned\"] - }}", globals::PAGE_LIMIT); - - params.push(("filter", strn!(filter))); - } else { - params.push(("since", since.clone())); - } - - let baseu = self.get_base_url()?; - let url = self.url("sync", params)?; - - let tx = self.tx.clone(); - let data = self.data.clone(); - - let attrs = json!(null); - - thread::spawn(move || { - match json_q("get", &url, &attrs, timeout) { - Ok(r) => { - let next_batch = String::from(r["next_batch"].as_str().unwrap_or("")); - if since.is_empty() { - let rooms = match get_rooms_from_json(r, &userid, &baseu) { - Ok(rs) => rs, - Err(err) => { - tx.send(BKResponse::SyncError(err)).unwrap(); - vec![] - } - }; - - let mut def: Option = 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()); - } - } - tx.send(BKResponse::Rooms(rooms, def)).unwrap(); - } else { - // Message events - match get_rooms_timeline_from_json(&baseu, &r) { - Ok(msgs) => tx.send(BKResponse::RoomMessages(msgs)).unwrap(), - Err(err) => tx.send(BKResponse::RoomMessagesError(err)).unwrap(), - }; - // Other events - match parse_sync_events(&r) { - Err(err) => tx.send(BKResponse::SyncError(err)).unwrap(), - Ok(events) => { - for ev in events { - match ev.stype.as_ref() { - "m.room.name" => { - let name = strn!(ev.content["name"].as_str().unwrap_or("")); - tx.send(BKResponse::RoomName(ev.room.clone(), name)).unwrap(); - } - "m.room.topic" => { - let t = strn!(ev.content["topic"].as_str().unwrap_or("")); - tx.send(BKResponse::RoomTopic(ev.room.clone(), t)).unwrap(); - } - "m.room.avatar" => { - tx.send(BKResponse::NewRoomAvatar(ev.room.clone())).unwrap(); - } - "m.room.member" => { - tx.send(BKResponse::RoomMemberEvent(ev)).unwrap(); - } - _ => { - println!("EVENT NOT MANAGED: {:?}", ev); - } - } - } - } - }; - } - - tx.send(BKResponse::Sync(next_batch.clone())).unwrap(); - data.lock().unwrap().since = next_batch; - }, - Err(err) => { tx.send(BKResponse::SyncError(err)).unwrap() } - }; - }); - - Ok(()) - } - - pub fn get_room_detail(&self, roomid: String, key: String) -> Result<(), Error> { - let url = self.url(&format!("rooms/{}/state/{}", roomid, key), vec![])?; - - let tx = self.tx.clone(); - let keys = key.clone(); - get!(&url, - |r: JsonValue| { - let mut value = String::from(""); - let k = keys.split('.').last().unwrap(); - - match r[&k].as_str() { - Some(x) => { value = String::from(x); }, - None => {} - } - tx.send(BKResponse::RoomDetail(roomid, key, value)).unwrap(); - }, - |err| { tx.send(BKResponse::RoomDetailError(err)).unwrap() } - ); - - Ok(()) - } - - pub fn get_room_avatar(&self, roomid: String) -> Result<(), Error> { - let userid = self.data.lock().unwrap().user_id.clone(); - let baseu = self.get_base_url()?; - let tk = self.data.lock().unwrap().access_token.clone(); - let url = self.url(&format!("rooms/{}/state/m.room.avatar", roomid), vec![])?; - - let tx = self.tx.clone(); - get!(&url, - |r: JsonValue| { - let avatar; - - match r["url"].as_str() { - Some(u) => { - avatar = thumb!(&baseu, u).unwrap_or(String::from("")); - }, - None => { - avatar = get_room_avatar(&baseu, &tk, &userid, &roomid) - .unwrap_or(String::from("")); - } - } - tx.send(BKResponse::RoomAvatar(roomid, avatar)).unwrap(); - }, - |err: Error| { - match err { - Error::MatrixError(ref js) if js["errcode"].as_str().unwrap_or("") == "M_NOT_FOUND" => { - let avatar = get_room_avatar(&baseu, &tk, &userid, &roomid) - .unwrap_or(String::from("")); - tx.send(BKResponse::RoomAvatar(roomid, avatar)).unwrap(); - }, - _ => { - tx.send(BKResponse::RoomAvatarError(err)).unwrap(); - } - } - } - ); - - Ok(()) - } - - pub fn get_room_messages(&self, roomid: String) -> Result<(), Error> { - let baseu = self.get_base_url()?; - let tk = self.data.lock().unwrap().access_token.clone(); - - let tx = self.tx.clone(); - thread::spawn(move || { - match get_initial_room_messages(&baseu, tk, roomid.clone(), - globals::PAGE_LIMIT as usize, - globals::PAGE_LIMIT, None) { - Ok((ms, _, _)) => { - tx.send(BKResponse::RoomMessagesInit(ms)).unwrap(); - } - Err(err) => { - tx.send(BKResponse::RoomMessagesError(err)).unwrap(); - } - } - }); - - Ok(()) - } - - pub fn get_message_context(&self, msg: Message) -> Result<(), Error> { - let url = self.url(&format!("rooms/{}/context/{}", msg.room, msg.id.unwrap_or_default()), - vec![("limit", String::from("40"))])?; - - let tx = self.tx.clone(); - let baseu = self.get_base_url()?; - let roomid = msg.room.clone(); - get!(&url, - |r: JsonValue| { - let mut ms: Vec = vec![]; - let array = r["events_before"].as_array(); - for msg in array.unwrap().iter().rev() { - if msg["type"].as_str().unwrap_or("") != "m.room.message" { - continue; - } - - let m = parse_room_message(&baseu, roomid.clone(), msg); - ms.push(m); - } - tx.send(BKResponse::RoomMessagesTo(ms)).unwrap(); - }, - |err| { tx.send(BKResponse::RoomMessagesError(err)).unwrap() } - ); - - Ok(()) - } - - pub fn get_room_members(&self, roomid: String) -> Result<(), Error> { - let url = self.url(&format!("rooms/{}/members", roomid), vec![])?; - - let tx = self.tx.clone(); - get!(&url, - |r: JsonValue| { - //println!("{:#?}", r); - let mut ms: Vec = vec![]; - for member in r["chunk"].as_array().unwrap().iter().rev() { - if member["type"].as_str().unwrap() != "m.room.member" { - continue; - } - - let content = &member["content"]; - if content["membership"].as_str().unwrap() != "join" { - continue; - } - - let m = Member { - alias: Some(String::from(content["displayname"].as_str().unwrap_or(""))), - uid: String::from(member["sender"].as_str().unwrap()), - avatar: Some(String::from(content["avatar_url"].as_str().unwrap_or(""))), - }; - ms.push(m); - } - tx.send(BKResponse::RoomMembers(ms)).unwrap(); - }, - |err| { tx.send(BKResponse::RoomMembersError(err)).unwrap() } - ); - - Ok(()) - } - - pub fn get_user_info_async(&mut self, - uid: &str, - tx: Sender<(String, String)>) - -> Result<(), Error> { - let baseu = self.get_base_url()?; - - let u = String::from(uid); - - if let Some(info) = self.user_info_cache.get(&u) { - let i = info.lock().unwrap().clone(); - if !i.0.is_empty() || !i.1.is_empty() { - tx.send(i).unwrap(); - return Ok(()) - } - } - - let info = Arc::new(Mutex::new((String::new(), String::new()))); - let cache_key = u.clone(); - let cache_value = info.clone(); - - thread::spawn(move || { - let i0 = info.lock(); - match get_user_avatar(&baseu, &u) { - Ok(info) => { - tx.send(info.clone()).unwrap(); - let mut i = i0.unwrap(); - i.0 = info.0; - i.1 = info.1; - } - Err(_) => { - tx.send((String::new(), String::new())).unwrap(); - } - }; - }); - - self.user_info_cache.insert(cache_key, cache_value); - - Ok(()) - } - - pub fn get_thumb_async(&self, media: String, tx: Sender) -> Result<(), Error> { - let baseu = self.get_base_url()?; - - thread::spawn(move || { - match thumb!(&baseu, &media) { - Ok(fname) => { - tx.send(fname).unwrap(); - } - Err(_) => { - tx.send(String::from("")).unwrap(); - } - }; - }); - - Ok(()) - } - - pub fn get_media(&self, media: String) -> Result<(), Error> { - let baseu = self.get_base_url()?; - - let tx = self.tx.clone(); - thread::spawn(move || { - match media!(&baseu, &media) { - Ok(fname) => { - tx.send(BKResponse::Media(fname)).unwrap(); - } - Err(err) => { - tx.send(BKResponse::MediaError(err)).unwrap(); - } - }; - }); - - Ok(()) - } - - pub fn send_msg(&self, msg: Message) -> Result<(), Error> { - let roomid = msg.room.clone(); - let msgid; - - { - let mut data = self.data.lock().unwrap(); - data.msgid = data.msgid + 1; - msgid = data.msgid; - } - - let url = self.url(&format!("rooms/{}/send/m.room.message/{}", roomid, msgid), vec![])?; - - let attrs = json!({ - "body": msg.body.clone(), - "url": msg.url.clone(), - "msgtype": msg.mtype.clone() - }); - - let tx = self.tx.clone(); - query!("put", &url, &attrs, - move |_| { - tx.send(BKResponse::SendMsg).unwrap(); - }, - |err| { tx.send(BKResponse::SendMsgError(err)).unwrap(); } - ); - - Ok(()) - } - - pub fn protocols(&self) -> Result<(), Error> { - let baseu = self.get_base_url()?; - let tk = self.data.lock().unwrap().access_token.clone(); - let mut url = baseu.join("/_matrix/client/unstable/thirdparty/protocols")?; - url.query_pairs_mut().clear() - .append_pair("access_token", &tk); - - let tx = self.tx.clone(); - let s = self.data.lock().unwrap().server_url.clone(); - get!(&url, - move |r: JsonValue| { - let mut protocols: Vec = vec![]; - - protocols.push(Protocol { - id: String::from(""), - desc: String::from(s.split('/').last().unwrap_or("")), - }); - - let prs = r.as_object().unwrap(); - for k in prs.keys() { - let ins = prs[k]["instances"].as_array().unwrap(); - for i in ins { - let p = Protocol{ - id: String::from(i["instance_id"].as_str().unwrap()), - desc: String::from(i["desc"].as_str().unwrap()), - }; - protocols.push(p); - } - } - - tx.send(BKResponse::DirectoryProtocols(protocols)).unwrap(); - }, - |err| { tx.send(BKResponse::DirectoryError(err)).unwrap(); } - ); - - Ok(()) - } - - pub fn room_search(&self, - query: Option, - third_party: Option, - more: bool) - -> Result<(), Error> { - - let url = self.url("publicRooms", vec![])?; - - let mut attrs = json!({"limit": 20}); - - if let Some(q) = query { - attrs["filter"] = json!({ - "generic_search_term": q - }); - } - - if let Some(tp) = third_party { - attrs["third_party_instance_id"] = json!(tp); - } - - if more { - let since = self.data.lock().unwrap().rooms_since.clone(); - attrs["since"] = json!(since); - } - - let tx = self.tx.clone(); - let data = self.data.clone(); - post!(&url, &attrs, - move |r: JsonValue| { - let next_branch = r["next_batch"].as_str().unwrap_or(""); - data.lock().unwrap().rooms_since = String::from(next_branch); - - 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 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, Some(name)); - r.alias = Some(alias); - r.avatar = Some(String::from(room["avatar_url"].as_str().unwrap_or(""))); - r.topic = Some(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); - } - - tx.send(BKResponse::DirectorySearch(rooms)).unwrap(); - }, - |err| { tx.send(BKResponse::DirectoryError(err)).unwrap(); } - ); - - Ok(()) - } - - pub fn join_room(&self, roomid: String) -> Result<(), Error> { - let url = self.url(&format!("rooms/{}/join", roomid), vec![])?; - - let tx = self.tx.clone(); - let data = self.data.clone(); - post!(&url, - move |_: JsonValue| { - data.lock().unwrap().join_to_room = roomid.clone(); - tx.send(BKResponse::JoinRoom).unwrap(); - }, - |err| { tx.send(BKResponse::JoinRoomError(err)).unwrap(); } - ); - - Ok(()) - } - - pub fn leave_room(&self, roomid: String) -> Result<(), Error> { - let url = self.url(&format!("rooms/{}/leave", roomid), vec![])?; - - let tx = self.tx.clone(); - post!(&url, - move |_: JsonValue| { - tx.send(BKResponse::LeaveRoom).unwrap(); - }, - |err| { tx.send(BKResponse::LeaveRoomError(err)).unwrap(); } - ); - - Ok(()) - } - - pub fn mark_as_read(&self, roomid: String, eventid: String) -> Result<(), Error> { - let url = self.url(&format!("rooms/{}/receipt/m.read/{}", roomid, eventid), vec![])?; - - 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(()) - } - - pub fn set_room_name(&self, roomid: String, name: String) -> Result<(), Error> { - let url = self.url(&format!("rooms/{}/state/m.room.name", roomid), vec![])?; - - let attrs = json!({ - "name": name, - }); - - let tx = self.tx.clone(); - query!("put", &url, &attrs, - |_| { tx.send(BKResponse::SetRoomName).unwrap(); }, - |err| { tx.send(BKResponse::SetRoomNameError(err)).unwrap(); } - ); - - Ok(()) - } - - pub fn set_room_topic(&self, roomid: String, topic: String) -> Result<(), Error> { - let url = self.url(&format!("rooms/{}/state/m.room.topic", roomid), vec![])?; - - let attrs = json!({ - "topic": topic, - }); - - let tx = self.tx.clone(); - query!("put", &url, &attrs, - |_| { tx.send(BKResponse::SetRoomTopic).unwrap(); }, - |err| { tx.send(BKResponse::SetRoomTopicError(err)).unwrap(); } - ); - - Ok(()) - } - - pub fn set_room_avatar(&self, roomid: String, avatar: String) -> Result<(), Error> { - let baseu = self.get_base_url()?; - let tk = self.data.lock().unwrap().access_token.clone(); - let params = vec![("access_token", tk.clone())]; - let mediaurl = media_url!(&baseu, "upload", params)?; - let roomurl = self.url(&format!("rooms/{}/state/m.room.avatar", roomid), vec![])?; - - let mut file = File::open(&avatar)?; - let mut contents: Vec = vec![]; - file.read_to_end(&mut contents)?; - - let tx = self.tx.clone(); - thread::spawn( - move || { - match put_media(mediaurl.as_str(), contents) { - Err(err) => { - tx.send(BKResponse::SetRoomAvatarError(err)).unwrap(); - } - Ok(js) => { - let uri = js["content_uri"].as_str().unwrap_or(""); - let attrs = json!({ "url": uri }); - match json_q("put", &roomurl, &attrs, 0) { - Ok(_) => { - tx.send(BKResponse::SetRoomAvatar).unwrap(); - }, - Err(err) => { - tx.send(BKResponse::SetRoomAvatarError(err)).unwrap(); - } - }; - } - }; - }, - ); - - Ok(()) - } - - pub fn attach_image(&self, roomid: String, image: Vec) -> Result<(), Error> { - self.attach_send(roomid, strn!("Screenshot"), image, "m.image") - } - - pub fn attach_file(&self, roomid: String, path: String) -> Result<(), Error> { - let mut file = File::open(&path)?; - let mut contents: Vec = vec![]; - file.read_to_end(&mut contents)?; - - let p: &Path = Path::new(&path); - let mime = tree_magic::from_filepath(p); - - let mtype = match mime.as_ref() { - "image/gif" => "m.image", - "image/png" => "m.image", - "image/jpeg" => "m.image", - "image/jpg" => "m.image", - _ => "m.file" - }; - - let body = strn!(path.split("/").last().unwrap_or(&path)); - self.attach_send(roomid, body, contents, mtype) - } - - pub fn attach_send(&self, roomid: String, body: String, contents: Vec, mtype: &str) -> Result<(), Error> { - let baseu = self.get_base_url()?; - let tk = self.data.lock().unwrap().access_token.clone(); - let params = vec![("access_token", tk.clone())]; - let mediaurl = media_url!(&baseu, "upload", params)?; - - let now = Local::now(); - let userid = self.data.lock().unwrap().user_id.clone(); - - let mut m = Message { - sender: userid, - mtype: strn!(mtype), - body: body, - room: roomid.clone(), - date: now, - thumb: None, - url: None, - id: None, - }; - - let tx = self.tx.clone(); - let itx = self.internal_tx.clone(); - thread::spawn( - move || { - match put_media(mediaurl.as_str(), contents) { - Err(err) => { - tx.send(BKResponse::AttachFileError(err)).unwrap(); - } - Ok(js) => { - let uri = js["content_uri"].as_str().unwrap_or(""); - m.url = Some(strn!(uri)); - if let Some(t) = itx { - t.send(BKCommand::SendMsg(m.clone())).unwrap(); - } - tx.send(BKResponse::AttachedFile(m)).unwrap(); - } - }; - }, - ); - - Ok(()) - } - - pub fn search(&self, roomid: String, term: Option) -> Result<(), Error> { - let tx = self.tx.clone(); - - match term { - Some(ref t) if !t.is_empty() => { - self.make_search(roomid, t.clone()) - } - _ => { - tx.send(BKResponse::SearchEnd).unwrap(); - self.get_room_messages(roomid) - } - } - } - - pub fn new_room(&self, name: String, privacy: RoomType) -> Result<(), Error> { - let url = self.url("createRoom", vec![])?; - let attrs = json!({ - "invite": [], - "invite_3pid": [], - "name": &name, - "visibility": match privacy { - RoomType::Public => "public", - RoomType::Private => "private", - }, - "topic": "", - "preset": match privacy { - RoomType::Public => "public_chat", - RoomType::Private => "private_chat", - }, - }); - - let n = name.clone(); - let tx = self.tx.clone(); - post!(&url, &attrs, - move |r: JsonValue| { - let id = strn!(r["room_id"].as_str().unwrap_or("")); - let name = n; - let r = Room::new(id, Some(name)); - tx.send(BKResponse::NewRoom(r)).unwrap(); - }, - |err| { tx.send(BKResponse::NewRoomError(err)).unwrap(); } - ); - Ok(()) - } - - pub fn make_search(&self, roomid: String, term: String) -> Result<(), Error> { - let url = self.url("search", vec![])?; - - let attrs = json!({ - "search_categories": { - "room_events": { - "keys": ["content.body"], - "search_term": term, - "filter": { - "rooms": [ roomid.clone() ], - }, - "order_by": "recent", - }, - }, - }); - - let tx = self.tx.clone(); - let baseu = self.get_base_url()?; - - thread::spawn(move || { - match json_q("post", &url, &attrs, 0) { - Ok(js) => { - tx.send(BKResponse::SearchEnd).unwrap(); - let mut ms: Vec = vec![]; - - let res = &js["search_categories"]["room_events"]["results"]; - for search in res.as_array().unwrap().iter().rev() { - let msg = &search["result"]; - if msg["type"].as_str().unwrap_or("") != "m.room.message" { - continue; - } - - let m = parse_room_message(&baseu, roomid.clone(), msg); - ms.push(m); - } - tx.send(BKResponse::RoomMessagesInit(ms)).unwrap(); - } - Err(err) => { - tx.send(BKResponse::SearchEnd).unwrap(); - tx.send(BKResponse::SearchError(err)).unwrap() - } - }; - }); - - Ok(()) - } -} diff --git a/fractal-api/src/backend/directory.rs b/fractal-api/src/backend/directory.rs new file mode 100644 index 00000000..becb5a41 --- /dev/null +++ b/fractal-api/src/backend/directory.rs @@ -0,0 +1,108 @@ +extern crate serde_json; + +use self::serde_json::Value as JsonValue; + +use globals; + +use std::thread; +use error::Error; +use backend::types::BKResponse; +use backend::types::Backend; + +use util::json_q; + +use types::Room; +use types::Protocol; + +pub fn protocols(bk: &Backend) -> Result<(), Error> { + let baseu = bk.get_base_url()?; + let tk = bk.data.lock().unwrap().access_token.clone(); + let mut url = baseu.join("/_matrix/client/unstable/thirdparty/protocols")?; + url.query_pairs_mut().clear() + .append_pair("access_token", &tk); + + let tx = bk.tx.clone(); + let s = bk.data.lock().unwrap().server_url.clone(); + get!(&url, + move |r: JsonValue| { + let mut protocols: Vec = vec![]; + + protocols.push(Protocol { + id: String::from(""), + desc: String::from(s.split('/').last().unwrap_or("")), + }); + + let prs = r.as_object().unwrap(); + for k in prs.keys() { + let ins = prs[k]["instances"].as_array().unwrap(); + for i in ins { + let p = Protocol{ + id: String::from(i["instance_id"].as_str().unwrap()), + desc: String::from(i["desc"].as_str().unwrap()), + }; + protocols.push(p); + } + } + + tx.send(BKResponse::DirectoryProtocols(protocols)).unwrap(); + }, + |err| { tx.send(BKResponse::DirectoryError(err)).unwrap(); } + ); + + Ok(()) +} + +pub fn room_search(bk: &Backend, + query: Option, + third_party: Option, + more: bool) + -> Result<(), Error> { + + let url = bk.url("publicRooms", vec![])?; + + let mut attrs = json!({"limit": 20}); + + if let Some(q) = query { + attrs["filter"] = json!({ + "generic_search_term": q + }); + } + + if let Some(tp) = third_party { + attrs["third_party_instance_id"] = json!(tp); + } + + if more { + let since = bk.data.lock().unwrap().rooms_since.clone(); + attrs["since"] = json!(since); + } + + let tx = bk.tx.clone(); + let data = bk.data.clone(); + post!(&url, &attrs, + move |r: JsonValue| { + let next_branch = r["next_batch"].as_str().unwrap_or(""); + data.lock().unwrap().rooms_since = String::from(next_branch); + + 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 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, Some(name)); + r.alias = Some(alias); + r.avatar = Some(String::from(room["avatar_url"].as_str().unwrap_or(""))); + r.topic = Some(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); + } + + tx.send(BKResponse::DirectorySearch(rooms)).unwrap(); + }, + |err| { tx.send(BKResponse::DirectoryError(err)).unwrap(); } + ); + + Ok(()) +} diff --git a/fractal-api/src/backend/media.rs b/fractal-api/src/backend/media.rs new file mode 100644 index 00000000..5cb7d455 --- /dev/null +++ b/fractal-api/src/backend/media.rs @@ -0,0 +1,43 @@ +use std::thread; +use std::sync::mpsc::Sender; +use error::Error; +use backend::types::BKResponse; +use backend::types::Backend; + +use util::dw_media; + +pub fn get_thumb_async(bk: &Backend, media: String, tx: Sender) -> Result<(), Error> { + let baseu = bk.get_base_url()?; + + thread::spawn(move || { + match thumb!(&baseu, &media) { + Ok(fname) => { + tx.send(fname).unwrap(); + } + Err(_) => { + tx.send(String::from("")).unwrap(); + } + }; + }); + + Ok(()) +} + +pub fn get_media(bk: &Backend, media: String) -> Result<(), Error> { + let baseu = bk.get_base_url()?; + + let tx = bk.tx.clone(); + thread::spawn(move || { + match media!(&baseu, &media) { + Ok(fname) => { + tx.send(BKResponse::Media(fname)).unwrap(); + } + Err(err) => { + tx.send(BKResponse::MediaError(err)).unwrap(); + } + }; + }); + + Ok(()) +} + diff --git a/fractal-api/src/backend/mod.rs b/fractal-api/src/backend/mod.rs new file mode 100644 index 00000000..97eae6b1 --- /dev/null +++ b/fractal-api/src/backend/mod.rs @@ -0,0 +1,238 @@ +extern crate url; + +use std::sync::{Arc, Mutex}; +use std::thread; +use self::url::Url; +use std::sync::mpsc::{Sender, Receiver}; +use std::sync::mpsc::channel; +use std::sync::mpsc::RecvError; + +use error::Error; + +use util::build_url; +use cache::CacheMap; + +mod types; +mod register; +mod user; +mod room; +mod sync; +mod media; +mod directory; + +pub use self::types::BKResponse; +pub use self::types::BKCommand; + +pub use self::types::Backend; +pub use self::types::BackendData; + + +impl Backend { + pub fn new(tx: Sender) -> Backend { + let data = BackendData { + user_id: String::from("Guest"), + access_token: String::from(""), + server_url: String::from("https://matrix.org"), + since: String::from(""), + msgid: 1, + rooms_since: String::from(""), + join_to_room: String::from(""), + }; + Backend { + tx: tx, + internal_tx: None, + data: Arc::new(Mutex::new(data)), + user_info_cache: CacheMap::new().timeout(60*60), + } + } + + fn get_base_url(&self) -> Result { + let s = self.data.lock().unwrap().server_url.clone(); + let url = Url::parse(&s)?; + Ok(url) + } + + fn url(&self, path: &str, params: Vec<(&str, String)>) -> Result { + let base = self.get_base_url()?; + let tk = self.data.lock().unwrap().access_token.clone(); + + let mut params2 = params.to_vec(); + params2.push(("access_token", tk.clone())); + + client_url!(&base, path, params2) + } + + pub fn run(mut self) -> Sender { + let (apptx, rx): (Sender, Receiver) = channel(); + + self.internal_tx = Some(apptx.clone()); + thread::spawn(move || loop { + let cmd = rx.recv(); + if !self.command_recv(cmd) { + break; + } + }); + + apptx + } + + pub fn command_recv(&mut self, cmd: Result) -> bool { + let tx = self.tx.clone(); + + match cmd { + // Register module + + Ok(BKCommand::Login(user, passwd, server)) => { + let r = register::login(self, user, passwd, server); + bkerror!(r, tx, BKResponse::LoginError); + } + Ok(BKCommand::Logout) => { + let r = register::logout(self); + bkerror!(r, tx, BKResponse::LogoutError); + } + Ok(BKCommand::Register(user, passwd, server)) => { + let r = register::register(self, user, passwd, server); + bkerror!(r, tx, BKResponse::LoginError); + } + Ok(BKCommand::Guest(server)) => { + let r = register::guest(self, server); + bkerror!(r, tx, BKResponse::GuestLoginError); + } + + // User module + + Ok(BKCommand::GetUsername) => { + let r = user::get_username(self); + bkerror!(r, tx, BKResponse::UserNameError); + } + Ok(BKCommand::GetAvatar) => { + let r = user::get_avatar(self); + bkerror!(r, tx, BKResponse::AvatarError); + } + Ok(BKCommand::GetUserInfoAsync(sender, ctx)) => { + let r = user::get_user_info_async(self, &sender, ctx); + bkerror!(r, tx, BKResponse::CommandError); + } + + // Sync module + + Ok(BKCommand::Sync) => { + let r = sync::sync(self); + bkerror!(r, tx, BKResponse::SyncError); + } + Ok(BKCommand::SyncForced) => { + let r = sync::force_sync(self); + bkerror!(r, tx, BKResponse::SyncError); + } + + // Room module + + Ok(BKCommand::GetRoomMessages(room)) => { + let r = room::get_room_messages(self, room); + bkerror!(r, tx, BKResponse::RoomMessagesError); + } + Ok(BKCommand::GetMessageContext(message)) => { + let r = room::get_message_context(self, message); + bkerror!(r, tx, BKResponse::RoomMessagesError); + } + Ok(BKCommand::SendMsg(msg)) => { + let r = room::send_msg(self, msg); + bkerror!(r, tx, BKResponse::SendMsgError); + } + Ok(BKCommand::SetRoom(room)) => { + let r = room::set_room(self, room); + bkerror!(r, tx, BKResponse::SetRoomError); + } + Ok(BKCommand::GetRoomAvatar(room)) => { + let r = room::get_room_avatar(self, room); + bkerror!(r, tx, BKResponse::GetRoomAvatarError); + } + Ok(BKCommand::JoinRoom(roomid)) => { + let r = room::join_room(self, roomid); + bkerror!(r, tx, BKResponse::JoinRoomError); + } + Ok(BKCommand::LeaveRoom(roomid)) => { + let r = room::leave_room(self, roomid); + bkerror!(r, tx, BKResponse::LeaveRoomError); + } + Ok(BKCommand::MarkAsRead(roomid, evid)) => { + let r = room::mark_as_read(self, roomid, evid); + bkerror!(r, tx, BKResponse::MarkAsReadError); + } + Ok(BKCommand::SetRoomName(roomid, name)) => { + let r = room::set_room_name(self, roomid, name); + bkerror!(r, tx, BKResponse::SetRoomNameError); + } + Ok(BKCommand::SetRoomTopic(roomid, topic)) => { + let r = room::set_room_topic(self, roomid, topic); + bkerror!(r, tx, BKResponse::SetRoomTopicError); + } + Ok(BKCommand::SetRoomAvatar(roomid, fname)) => { + let r = room::set_room_avatar(self, roomid, fname); + bkerror!(r, tx, BKResponse::SetRoomAvatarError); + } + Ok(BKCommand::AttachFile(roomid, fname)) => { + let r = room::attach_file(self, roomid, fname); + bkerror!(r, tx, BKResponse::AttachFileError); + } + Ok(BKCommand::AttachImage(roomid, image)) => { + let r = room::attach_image(self, roomid, image); + bkerror!(r, tx, BKResponse::AttachFileError); + } + Ok(BKCommand::NewRoom(name, privacy)) => { + let r = room::new_room(self, name, privacy); + bkerror!(r, tx, BKResponse::NewRoomError); + } + Ok(BKCommand::Search(roomid, term)) => { + let r = room::search(self, roomid, term); + bkerror!(r, tx, BKResponse::SearchError); + } + + // Media module + + Ok(BKCommand::GetThumbAsync(media, ctx)) => { + let r = media::get_thumb_async(self, media, ctx); + bkerror!(r, tx, BKResponse::CommandError); + } + Ok(BKCommand::GetMedia(media)) => { + let r = media::get_media(self, media); + bkerror!(r, tx, BKResponse::CommandError); + } + + // Directory module + + Ok(BKCommand::DirectoryProtocols) => { + let r = directory::protocols(self); + bkerror!(r, tx, BKResponse::DirectoryError); + } + Ok(BKCommand::DirectorySearch(dq, dtp, more)) => { + let q = match dq { + ref a if a.is_empty() => None, + b => Some(b), + }; + + let tp = match dtp { + ref a if a.is_empty() => None, + b => Some(b), + }; + + let r = directory::room_search(self, q, tp, more); + bkerror!(r, tx, BKResponse::DirectoryError); + } + + // Internal commands + + Ok(BKCommand::NotifyClicked(message)) => { + tx.send(BKResponse::NotificationClicked(message)).unwrap(); + } + Ok(BKCommand::ShutDown) => { + return false; + } + Err(_) => { + return false; + } + }; + + true + } +} diff --git a/fractal-api/src/backend/register.rs b/fractal-api/src/backend/register.rs new file mode 100644 index 00000000..008b5ae4 --- /dev/null +++ b/fractal-api/src/backend/register.rs @@ -0,0 +1,121 @@ +extern crate url; +extern crate serde_json; + +use self::serde_json::Value as JsonValue; + +use std::thread; +use self::url::Url; + +use util::json_q; +use globals; +use error::Error; + +use backend::types::BKResponse; +use backend::types::Backend; + + +pub fn guest(bk: &Backend, server: String) -> Result<(), Error> { + let s = server.clone(); + let url = Url::parse(&s).unwrap().join("/_matrix/client/r0/register?kind=guest")?; + bk.data.lock().unwrap().server_url = s; + + let data = bk.data.clone(); + let tx = bk.tx.clone(); + let attrs = json!({}); + post!(&url, &attrs, + |r: JsonValue| { + let uid = String::from(r["user_id"].as_str().unwrap_or("")); + let tk = String::from(r["access_token"].as_str().unwrap_or("")); + data.lock().unwrap().user_id = uid.clone(); + data.lock().unwrap().access_token = tk.clone(); + data.lock().unwrap().since = String::from(""); + tx.send(BKResponse::Token(uid, tk)).unwrap(); + tx.send(BKResponse::Rooms(vec![], None)).unwrap(); + }, + |err| tx.send(BKResponse::GuestLoginError(err)).unwrap()); + + Ok(()) +} + +pub fn login(bk: &Backend, user: String, password: String, server: String) -> Result<(), Error> { + let s = server.clone(); + bk.data.lock().unwrap().server_url = s; + let url = bk.url("login", vec![])?; + + let attrs = json!({ + "type": "m.login.password", + "user": user, + "password": password + }); + + let data = bk.data.clone(); + + let tx = bk.tx.clone(); + post!(&url, &attrs, + |r: JsonValue| { + let uid = String::from(r["user_id"].as_str().unwrap_or("")); + let tk = String::from(r["access_token"].as_str().unwrap_or("")); + + if uid.is_empty() || tk.is_empty() { + tx.send(BKResponse::LoginError(Error::BackendError)).unwrap(); + } else { + data.lock().unwrap().user_id = uid.clone(); + data.lock().unwrap().access_token = tk.clone(); + data.lock().unwrap().since = String::new(); + tx.send(BKResponse::Token(uid, tk)).unwrap(); + } + }, + |err| { tx.send(BKResponse::LoginError(err)).unwrap() } + ); + + Ok(()) +} + +pub fn logout(bk: &Backend) -> Result<(), Error> { + let url = bk.url("logout", vec![])?; + let attrs = json!({}); + + let data = bk.data.clone(); + let tx = bk.tx.clone(); + post!(&url, &attrs, + |_| { + data.lock().unwrap().user_id = String::new(); + data.lock().unwrap().access_token = String::new(); + data.lock().unwrap().since = String::new(); + tx.send(BKResponse::Logout).unwrap(); + }, + |err| { tx.send(BKResponse::LogoutError(err)).unwrap() } + ); + Ok(()) +} + +pub fn register(bk: &Backend, user: String, password: String, server: String) -> Result<(), Error> { + let s = server.clone(); + bk.data.lock().unwrap().server_url = s; + let url = bk.url("register", vec![("kind", strn!("user"))])?; + + let attrs = json!({ + "auth": {"type": "m.login.password"}, + "username": user, + "bind_email": false, + "password": password + }); + + let data = bk.data.clone(); + let tx = bk.tx.clone(); + post!(&url, &attrs, + |r: JsonValue| { + println!("RESPONSE: {:#?}", r); + let uid = String::from(r["user_id"].as_str().unwrap_or("")); + let tk = String::from(r["access_token"].as_str().unwrap_or("")); + + data.lock().unwrap().user_id = uid.clone(); + data.lock().unwrap().access_token = tk.clone(); + data.lock().unwrap().since = String::from(""); + tx.send(BKResponse::Token(uid, tk)).unwrap(); + }, + |err| { tx.send(BKResponse::LoginError(err)).unwrap() } + ); + + Ok(()) +} diff --git a/fractal-api/src/backend/room.rs b/fractal-api/src/backend/room.rs new file mode 100644 index 00000000..b82d9315 --- /dev/null +++ b/fractal-api/src/backend/room.rs @@ -0,0 +1,483 @@ +extern crate serde_json; +extern crate tree_magic; +extern crate chrono; + +use self::chrono::prelude::*; +use std::fs::File; +use std::io::prelude::*; +use std::path::Path; + +use globals; +use std::thread; +use error::Error; + +use util::json_q; +use util::dw_media; +use util::get_initial_room_messages; +use util::parse_room_message; +use util::build_url; +use util::put_media; +use util; + +use backend::types::Backend; +use backend::types::BKResponse; +use backend::types::BKCommand; +use backend::types::RoomType; +use backend::room; + +use types::Room; +use types::Member; +use types::Message; + +use self::serde_json::Value as JsonValue; + +pub fn set_room(bk: &Backend, room: Room) -> Result<(), Error> { + get_room_detail(bk, room.id.clone(), String::from("m.room.topic"))?; + get_room_avatar(bk, room.id.clone())?; + get_room_members(bk, room.id.clone())?; + + Ok(()) +} + +pub fn get_room_detail(bk: &Backend, roomid: String, key: String) -> Result<(), Error> { + let url = bk.url(&format!("rooms/{}/state/{}", roomid, key), vec![])?; + + let tx = bk.tx.clone(); + let keys = key.clone(); + get!(&url, + |r: JsonValue| { + let mut value = String::from(""); + let k = keys.split('.').last().unwrap(); + + match r[&k].as_str() { + Some(x) => { value = String::from(x); }, + None => {} + } + tx.send(BKResponse::RoomDetail(roomid, key, value)).unwrap(); + }, + |err| { tx.send(BKResponse::RoomDetailError(err)).unwrap() } + ); + + Ok(()) +} + +pub fn get_room_avatar(bk: &Backend, roomid: String) -> Result<(), Error> { + let userid = bk.data.lock().unwrap().user_id.clone(); + let baseu = bk.get_base_url()?; + let tk = bk.data.lock().unwrap().access_token.clone(); + let url = bk.url(&format!("rooms/{}/state/m.room.avatar", roomid), vec![])?; + + let tx = bk.tx.clone(); + get!(&url, + |r: JsonValue| { + let avatar; + + match r["url"].as_str() { + Some(u) => { + avatar = thumb!(&baseu, u).unwrap_or(String::from("")); + }, + None => { + avatar = util::get_room_avatar(&baseu, &tk, &userid, &roomid) + .unwrap_or(String::from("")); + } + } + tx.send(BKResponse::RoomAvatar(roomid, avatar)).unwrap(); + }, + |err: Error| { + match err { + Error::MatrixError(ref js) if js["errcode"].as_str().unwrap_or("") == "M_NOT_FOUND" => { + let avatar = util::get_room_avatar(&baseu, &tk, &userid, &roomid) + .unwrap_or(String::from("")); + tx.send(BKResponse::RoomAvatar(roomid, avatar)).unwrap(); + }, + _ => { + tx.send(BKResponse::RoomAvatarError(err)).unwrap(); + } + } + } + ); + + Ok(()) +} + +pub fn get_room_members(bk: &Backend, roomid: String) -> Result<(), Error> { + let url = bk.url(&format!("rooms/{}/members", roomid), vec![])?; + + let tx = bk.tx.clone(); + get!(&url, + |r: JsonValue| { + //println!("{:#?}", r); + let mut ms: Vec = vec![]; + for member in r["chunk"].as_array().unwrap().iter().rev() { + if member["type"].as_str().unwrap() != "m.room.member" { + continue; + } + + let content = &member["content"]; + if content["membership"].as_str().unwrap() != "join" { + continue; + } + + let m = Member { + alias: Some(String::from(content["displayname"].as_str().unwrap_or(""))), + uid: String::from(member["sender"].as_str().unwrap()), + avatar: Some(String::from(content["avatar_url"].as_str().unwrap_or(""))), + }; + ms.push(m); + } + tx.send(BKResponse::RoomMembers(ms)).unwrap(); + }, + |err| { tx.send(BKResponse::RoomMembersError(err)).unwrap() } + ); + + Ok(()) +} + +pub fn get_room_messages(bk: &Backend, roomid: String) -> Result<(), Error> { + let baseu = bk.get_base_url()?; + let tk = bk.data.lock().unwrap().access_token.clone(); + + let tx = bk.tx.clone(); + thread::spawn(move || { + match get_initial_room_messages(&baseu, tk, roomid.clone(), + globals::PAGE_LIMIT as usize, + globals::PAGE_LIMIT, None) { + Ok((ms, _, _)) => { + tx.send(BKResponse::RoomMessagesInit(ms)).unwrap(); + } + Err(err) => { + tx.send(BKResponse::RoomMessagesError(err)).unwrap(); + } + } + }); + + Ok(()) +} + +pub fn get_message_context(bk: &Backend, msg: Message) -> Result<(), Error> { + let url = bk.url(&format!("rooms/{}/context/{}", msg.room, msg.id.unwrap_or_default()), + vec![("limit", String::from("40"))])?; + + let tx = bk.tx.clone(); + let baseu = bk.get_base_url()?; + let roomid = msg.room.clone(); + get!(&url, + |r: JsonValue| { + let mut ms: Vec = vec![]; + let array = r["events_before"].as_array(); + for msg in array.unwrap().iter().rev() { + if msg["type"].as_str().unwrap_or("") != "m.room.message" { + continue; + } + + let m = parse_room_message(&baseu, roomid.clone(), msg); + ms.push(m); + } + tx.send(BKResponse::RoomMessagesTo(ms)).unwrap(); + }, + |err| { tx.send(BKResponse::RoomMessagesError(err)).unwrap() } + ); + + Ok(()) +} + +pub fn send_msg(bk: &Backend, msg: Message) -> Result<(), Error> { + let roomid = msg.room.clone(); + let msgid; + + { + let mut data = bk.data.lock().unwrap(); + data.msgid = data.msgid + 1; + msgid = data.msgid; + } + + let url = bk.url(&format!("rooms/{}/send/m.room.message/{}", roomid, msgid), vec![])?; + + let attrs = json!({ + "body": msg.body.clone(), + "url": msg.url.clone(), + "msgtype": msg.mtype.clone() + }); + + let tx = bk.tx.clone(); + query!("put", &url, &attrs, + move |_| { + tx.send(BKResponse::SendMsg).unwrap(); + }, + |err| { tx.send(BKResponse::SendMsgError(err)).unwrap(); } + ); + + Ok(()) +} + +pub fn join_room(bk: &Backend, roomid: String) -> Result<(), Error> { + let url = bk.url(&format!("rooms/{}/join", roomid), vec![])?; + + let tx = bk.tx.clone(); + let data = bk.data.clone(); + post!(&url, + move |_: JsonValue| { + data.lock().unwrap().join_to_room = roomid.clone(); + tx.send(BKResponse::JoinRoom).unwrap(); + }, + |err| { tx.send(BKResponse::JoinRoomError(err)).unwrap(); } + ); + + Ok(()) +} + +pub fn leave_room(bk: &Backend, roomid: String) -> Result<(), Error> { + let url = bk.url(&format!("rooms/{}/leave", roomid), vec![])?; + + let tx = bk.tx.clone(); + post!(&url, + move |_: JsonValue| { + tx.send(BKResponse::LeaveRoom).unwrap(); + }, + |err| { tx.send(BKResponse::LeaveRoomError(err)).unwrap(); } + ); + + Ok(()) +} + +pub fn mark_as_read(bk: &Backend, roomid: String, eventid: String) -> Result<(), Error> { + let url = bk.url(&format!("rooms/{}/receipt/m.read/{}", roomid, eventid), vec![])?; + + let tx = bk.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(()) +} + +pub fn set_room_name(bk: &Backend, roomid: String, name: String) -> Result<(), Error> { + let url = bk.url(&format!("rooms/{}/state/m.room.name", roomid), vec![])?; + + let attrs = json!({ + "name": name, + }); + + let tx = bk.tx.clone(); + query!("put", &url, &attrs, + |_| { tx.send(BKResponse::SetRoomName).unwrap(); }, + |err| { tx.send(BKResponse::SetRoomNameError(err)).unwrap(); } + ); + + Ok(()) +} + +pub fn set_room_topic(bk: &Backend, roomid: String, topic: String) -> Result<(), Error> { + let url = bk.url(&format!("rooms/{}/state/m.room.topic", roomid), vec![])?; + + let attrs = json!({ + "topic": topic, + }); + + let tx = bk.tx.clone(); + query!("put", &url, &attrs, + |_| { tx.send(BKResponse::SetRoomTopic).unwrap(); }, + |err| { tx.send(BKResponse::SetRoomTopicError(err)).unwrap(); } + ); + + Ok(()) +} + +pub fn set_room_avatar(bk: &Backend, roomid: String, avatar: String) -> Result<(), Error> { + let baseu = bk.get_base_url()?; + let tk = bk.data.lock().unwrap().access_token.clone(); + let params = vec![("access_token", tk.clone())]; + let mediaurl = media_url!(&baseu, "upload", params)?; + let roomurl = bk.url(&format!("rooms/{}/state/m.room.avatar", roomid), vec![])?; + + let mut file = File::open(&avatar)?; + let mut contents: Vec = vec![]; + file.read_to_end(&mut contents)?; + + let tx = bk.tx.clone(); + thread::spawn( + move || { + match put_media(mediaurl.as_str(), contents) { + Err(err) => { + tx.send(BKResponse::SetRoomAvatarError(err)).unwrap(); + } + Ok(js) => { + let uri = js["content_uri"].as_str().unwrap_or(""); + let attrs = json!({ "url": uri }); + match json_q("put", &roomurl, &attrs, 0) { + Ok(_) => { + tx.send(BKResponse::SetRoomAvatar).unwrap(); + }, + Err(err) => { + tx.send(BKResponse::SetRoomAvatarError(err)).unwrap(); + } + }; + } + }; + }, + ); + + Ok(()) +} + +pub fn attach_image(bk: &Backend, roomid: String, image: Vec) -> Result<(), Error> { + attach_send(bk, roomid, strn!("Screenshot"), image, "m.image") +} + +pub fn attach_file(bk: &Backend, roomid: String, path: String) -> Result<(), Error> { + let mut file = File::open(&path)?; + let mut contents: Vec = vec![]; + file.read_to_end(&mut contents)?; + + let p: &Path = Path::new(&path); + let mime = tree_magic::from_filepath(p); + + let mtype = match mime.as_ref() { + "image/gif" => "m.image", + "image/png" => "m.image", + "image/jpeg" => "m.image", + "image/jpg" => "m.image", + _ => "m.file" + }; + + let body = strn!(path.split("/").last().unwrap_or(&path)); + attach_send(bk, roomid, body, contents, mtype) +} + +pub fn attach_send(bk: &Backend, roomid: String, body: String, contents: Vec, mtype: &str) -> Result<(), Error> { + let baseu = bk.get_base_url()?; + let tk = bk.data.lock().unwrap().access_token.clone(); + let params = vec![("access_token", tk.clone())]; + let mediaurl = media_url!(&baseu, "upload", params)?; + + let now = Local::now(); + let userid = bk.data.lock().unwrap().user_id.clone(); + + let mut m = Message { + sender: userid, + mtype: strn!(mtype), + body: body, + room: roomid.clone(), + date: now, + thumb: None, + url: None, + id: None, + }; + + let tx = bk.tx.clone(); + let itx = bk.internal_tx.clone(); + thread::spawn( + move || { + match put_media(mediaurl.as_str(), contents) { + Err(err) => { + tx.send(BKResponse::AttachFileError(err)).unwrap(); + } + Ok(js) => { + let uri = js["content_uri"].as_str().unwrap_or(""); + m.url = Some(strn!(uri)); + if let Some(t) = itx { + t.send(BKCommand::SendMsg(m.clone())).unwrap(); + } + tx.send(BKResponse::AttachedFile(m)).unwrap(); + } + }; + }, + ); + + Ok(()) +} + +pub fn new_room(bk: &Backend, name: String, privacy: RoomType) -> Result<(), Error> { + let url = bk.url("createRoom", vec![])?; + let attrs = json!({ + "invite": [], + "invite_3pid": [], + "name": &name, + "visibility": match privacy { + RoomType::Public => "public", + RoomType::Private => "private", + }, + "topic": "", + "preset": match privacy { + RoomType::Public => "public_chat", + RoomType::Private => "private_chat", + }, + }); + + let n = name.clone(); + let tx = bk.tx.clone(); + post!(&url, &attrs, + move |r: JsonValue| { + let id = strn!(r["room_id"].as_str().unwrap_or("")); + let name = n; + let r = Room::new(id, Some(name)); + tx.send(BKResponse::NewRoom(r)).unwrap(); + }, + |err| { tx.send(BKResponse::NewRoomError(err)).unwrap(); } + ); + Ok(()) +} + +pub fn search(bk: &Backend, roomid: String, term: Option) -> Result<(), Error> { + let tx = bk.tx.clone(); + + match term { + Some(ref t) if !t.is_empty() => { + make_search(bk, roomid, t.clone()) + } + _ => { + tx.send(BKResponse::SearchEnd).unwrap(); + room::get_room_messages(bk, roomid) + } + } +} + +pub fn make_search(bk: &Backend, roomid: String, term: String) -> Result<(), Error> { + let url = bk.url("search", vec![])?; + + let attrs = json!({ + "search_categories": { + "room_events": { + "keys": ["content.body"], + "search_term": term, + "filter": { + "rooms": [ roomid.clone() ], + }, + "order_by": "recent", + }, + }, + }); + + let tx = bk.tx.clone(); + let baseu = bk.get_base_url()?; + + thread::spawn(move || { + match json_q("post", &url, &attrs, 0) { + Ok(js) => { + tx.send(BKResponse::SearchEnd).unwrap(); + let mut ms: Vec = vec![]; + + let res = &js["search_categories"]["room_events"]["results"]; + for search in res.as_array().unwrap().iter().rev() { + let msg = &search["result"]; + if msg["type"].as_str().unwrap_or("") != "m.room.message" { + continue; + } + + let m = parse_room_message(&baseu, roomid.clone(), msg); + ms.push(m); + } + tx.send(BKResponse::RoomMessagesInit(ms)).unwrap(); + } + Err(err) => { + tx.send(BKResponse::SearchEnd).unwrap(); + tx.send(BKResponse::SearchError(err)).unwrap() + } + }; + }); + + Ok(()) +} diff --git a/fractal-api/src/backend/sync.rs b/fractal-api/src/backend/sync.rs new file mode 100644 index 00000000..93fae9b2 --- /dev/null +++ b/fractal-api/src/backend/sync.rs @@ -0,0 +1,126 @@ +use globals; +use std::thread; +use error::Error; +use util::json_q; +use util::get_rooms_from_json; +use util::get_rooms_timeline_from_json; +use util::parse_sync_events; +use backend::types::BKResponse; +use backend::types::Backend; +use types::Room; + +pub fn sync(bk: &Backend) -> Result<(), Error> { + let tk = bk.data.lock().unwrap().access_token.clone(); + if tk.is_empty() { + return Err(Error::BackendError); + } + + let since = bk.data.lock().unwrap().since.clone(); + let userid = bk.data.lock().unwrap().user_id.clone(); + + let mut params: Vec<(&str, String)> = vec![]; + let timeout = 120; + + params.push(("full_state", strn!("false"))); + params.push(("timeout", strn!("30000"))); + + if since.is_empty() { + let filter = format!("{{ + \"room\": {{ + \"state\": {{ + \"types\": [\"m.room.*\"], + }}, + \"timeline\": {{ + \"types\": [\"m.room.message\"], + \"limit\": {}, + }}, + \"ephemeral\": {{ \"types\": [] }} + }}, + \"presence\": {{ \"types\": [] }}, + \"event_format\": \"client\", + \"event_fields\": [\"type\", \"content\", \"sender\", \"event_id\", \"age\", \"unsigned\"] + }}", globals::PAGE_LIMIT); + + params.push(("filter", strn!(filter))); + } else { + params.push(("since", since.clone())); + } + + let baseu = bk.get_base_url()?; + let url = bk.url("sync", params)?; + + let tx = bk.tx.clone(); + let data = bk.data.clone(); + + let attrs = json!(null); + + thread::spawn(move || { + match json_q("get", &url, &attrs, timeout) { + Ok(r) => { + let next_batch = String::from(r["next_batch"].as_str().unwrap_or("")); + if since.is_empty() { + let rooms = match get_rooms_from_json(r, &userid, &baseu) { + Ok(rs) => rs, + Err(err) => { + tx.send(BKResponse::SyncError(err)).unwrap(); + vec![] + } + }; + + let mut def: Option = 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()); + } + } + tx.send(BKResponse::Rooms(rooms, def)).unwrap(); + } else { + // Message events + match get_rooms_timeline_from_json(&baseu, &r) { + Ok(msgs) => tx.send(BKResponse::RoomMessages(msgs)).unwrap(), + Err(err) => tx.send(BKResponse::RoomMessagesError(err)).unwrap(), + }; + // Other events + match parse_sync_events(&r) { + Err(err) => tx.send(BKResponse::SyncError(err)).unwrap(), + Ok(events) => { + for ev in events { + match ev.stype.as_ref() { + "m.room.name" => { + let name = strn!(ev.content["name"].as_str().unwrap_or("")); + tx.send(BKResponse::RoomName(ev.room.clone(), name)).unwrap(); + } + "m.room.topic" => { + let t = strn!(ev.content["topic"].as_str().unwrap_or("")); + tx.send(BKResponse::RoomTopic(ev.room.clone(), t)).unwrap(); + } + "m.room.avatar" => { + tx.send(BKResponse::NewRoomAvatar(ev.room.clone())).unwrap(); + } + "m.room.member" => { + tx.send(BKResponse::RoomMemberEvent(ev)).unwrap(); + } + _ => { + println!("EVENT NOT MANAGED: {:?}", ev); + } + } + } + } + }; + } + + tx.send(BKResponse::Sync(next_batch.clone())).unwrap(); + data.lock().unwrap().since = next_batch; + }, + Err(err) => { tx.send(BKResponse::SyncError(err)).unwrap() } + }; + }); + + Ok(()) +} + +pub fn force_sync(bk: &Backend) -> Result<(), Error> { + bk.data.lock().unwrap().since = String::from(""); + sync(bk) +} diff --git a/fractal-api/src/backend/types.rs b/fractal-api/src/backend/types.rs new file mode 100644 index 00000000..30669651 --- /dev/null +++ b/fractal-api/src/backend/types.rs @@ -0,0 +1,146 @@ +use std::sync::{Arc, Mutex}; +use std::sync::mpsc::Sender; + +use error::Error; + +use types::Message; +use types::Member; +use types::Protocol; +use types::Room; +use types::Event; + +use cache::CacheMap; + + +#[derive(Debug)] +pub enum BKCommand { + Login(String, String, String), + Logout, + #[allow(dead_code)] + Register(String, String, String), + #[allow(dead_code)] + Guest(String), + GetUsername, + GetAvatar, + Sync, + SyncForced, + GetRoomMessages(String), + GetMessageContext(Message), + GetRoomAvatar(String), + GetThumbAsync(String, Sender), + GetMedia(String), + GetUserInfoAsync(String, Sender<(String, String)>), + SendMsg(Message), + SetRoom(Room), + ShutDown, + DirectoryProtocols, + DirectorySearch(String, String, bool), + JoinRoom(String), + MarkAsRead(String, String), + LeaveRoom(String), + SetRoomName(String, String), + SetRoomTopic(String, String), + SetRoomAvatar(String, String), + AttachFile(String, String), + AttachImage(String, Vec), + Search(String, Option), + NotifyClicked(Message), + NewRoom(String, RoomType), +} + +#[derive(Debug)] +pub enum BKResponse { + Token(String, String), + Logout, + Name(String), + Avatar(String), + Sync(String), + Rooms(Vec, Option), + RoomDetail(String, String, String), + RoomAvatar(String, String), + NewRoomAvatar(String), + RoomMemberEvent(Event), + RoomMessages(Vec), + RoomMessagesInit(Vec), + RoomMessagesTo(Vec), + RoomMembers(Vec), + SendMsg, + DirectoryProtocols(Vec), + DirectorySearch(Vec), + JoinRoom, + LeaveRoom, + MarkedAsRead(String, String), + SetRoomName, + SetRoomTopic, + SetRoomAvatar, + RoomName(String, String), + RoomTopic(String, String), + Media(String), + AttachedFile(Message), + SearchEnd, + NotificationClicked(Message), + NewRoom(Room), + + //errors + UserNameError(Error), + AvatarError(Error), + LoginError(Error), + LogoutError(Error), + GuestLoginError(Error), + SyncError(Error), + RoomDetailError(Error), + RoomAvatarError(Error), + RoomMessagesError(Error), + RoomMembersError(Error), + SendMsgError(Error), + SetRoomError(Error), + CommandError(Error), + DirectoryError(Error), + JoinRoomError(Error), + MarkAsReadError(Error), + LeaveRoomError(Error), + SetRoomNameError(Error), + SetRoomTopicError(Error), + SetRoomAvatarError(Error), + GetRoomAvatarError(Error), + MediaError(Error), + AttachFileError(Error), + SearchError(Error), + NewRoomError(Error), +} + +#[derive(Debug)] +pub enum RoomType { + Public, + Private, +} + +pub struct BackendData { + pub user_id: String, + pub access_token: String, + pub server_url: String, + pub since: String, + pub msgid: i32, + pub rooms_since: String, + pub join_to_room: String, +} + +pub struct Backend { + pub tx: Sender, + pub data: Arc>, + pub internal_tx: Option>, + + // user info cache, uid -> (name, avatar) + pub user_info_cache: CacheMap>>, +} + +impl Clone for Backend { + fn clone(&self) -> Backend { + Backend { + tx: self.tx.clone(), + data: self.data.clone(), + internal_tx: self.internal_tx.clone(), + user_info_cache: self.user_info_cache.clone(), + } + } +} diff --git a/fractal-api/src/backend/user.rs b/fractal-api/src/backend/user.rs new file mode 100644 index 00000000..0b1e00f2 --- /dev/null +++ b/fractal-api/src/backend/user.rs @@ -0,0 +1,85 @@ +extern crate serde_json; + +use globals; +use std::thread; +use std::sync::{Arc, Mutex}; +use std::sync::mpsc::Sender; +use error::Error; +use util::json_q; +use util::get_user_avatar; +use backend::types::BKResponse; +use backend::types::Backend; + +use self::serde_json::Value as JsonValue; + +pub fn get_username(bk: &Backend) -> Result<(), Error> { + let id = bk.data.lock().unwrap().user_id.clone(); + let url = bk.url(&format!("profile/{}/displayname", id.clone()), vec![])?; + let tx = bk.tx.clone(); + get!(&url, + |r: JsonValue| { + let name = String::from(r["displayname"].as_str().unwrap_or(&id)); + tx.send(BKResponse::Name(name)).unwrap(); + }, + |err| { tx.send(BKResponse::UserNameError(err)).unwrap() } + ); + + Ok(()) +} + +pub fn get_avatar(bk: &Backend) -> Result<(), Error> { + let baseu = bk.get_base_url()?; + let userid = bk.data.lock().unwrap().user_id.clone(); + + let tx = bk.tx.clone(); + thread::spawn(move || match get_user_avatar(&baseu, &userid) { + Ok((_, fname)) => { + tx.send(BKResponse::Avatar(fname)).unwrap(); + } + Err(err) => { + tx.send(BKResponse::AvatarError(err)).unwrap(); + } + }); + + Ok(()) +} + +pub fn get_user_info_async(bk: &mut Backend, + uid: &str, + tx: Sender<(String, String)>) + -> Result<(), Error> { + let baseu = bk.get_base_url()?; + + let u = String::from(uid); + + if let Some(info) = bk.user_info_cache.get(&u) { + let i = info.lock().unwrap().clone(); + if !i.0.is_empty() || !i.1.is_empty() { + tx.send(i).unwrap(); + return Ok(()) + } + } + + let info = Arc::new(Mutex::new((String::new(), String::new()))); + let cache_key = u.clone(); + let cache_value = info.clone(); + + thread::spawn(move || { + let i0 = info.lock(); + match get_user_avatar(&baseu, &u) { + Ok(info) => { + tx.send(info.clone()).unwrap(); + let mut i = i0.unwrap(); + i.0 = info.0; + i.1 = info.1; + } + Err(_) => { + tx.send((String::new(), String::new())).unwrap(); + } + }; + }); + + bk.user_info_cache.insert(cache_key, cache_value); + + Ok(()) +} diff --git a/fractal-api/src/cache.rs b/fractal-api/src/cache.rs index 98d7cccb..2fe7fab5 100644 --- a/fractal-api/src/cache.rs +++ b/fractal-api/src/cache.rs @@ -2,12 +2,12 @@ use std::collections::HashMap; use std::time::Instant; -pub struct CacheMap { +pub struct CacheMap { map: HashMap, timeout: u64, } -impl CacheMap { +impl CacheMap { pub fn new() -> CacheMap { CacheMap { map: HashMap::new(), timeout: 10 } } @@ -34,3 +34,18 @@ impl CacheMap { self.map.insert(k, (now, v)); } } + +impl Clone for CacheMap { + fn clone(&self) -> CacheMap { + let mut map: CacheMap = CacheMap{ + map: HashMap::new(), + timeout: self.timeout, + }; + + for (k, v) in self.map.iter() { + map.map.insert(k.clone(), (v.0.clone(), v.1.clone())); + } + + map + } +}