Commit d38e966f authored by Daniel García Moreno's avatar Daniel García Moreno

Split the backend into several modules

See #30

I've split the backend file into several modules, by matrix.org API
funcionality. Now each function receives the backend struct as first
argument and this struct is clonable so we can try to send between
threads. The backend.data is a Arc<Mutex>> so it's shared between calls
and keep the shared information.

 * backend/types.rs: enums and structs, BKCommand, BKResponse and Backend
 * backend/mod.rs: Backend struct methods, new, run and command_recv
 * backend/register.rs: Login related API calls
 * backend/user.rs: User related API calls
 * backend/media.rs: Media related API calls
 * backend/directory.rs: Directory related API calls
 * backend/room.rs: Room related API calls
 * backend/sync.rs: Sync related API calls
parent 067d1a40
Pipeline #1331 failed with stage
in 25 minutes and 35 seconds
This diff is collapsed.
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<Protocol> = 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<String>,
third_party: Option<String>,
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<Room> = 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(())
}
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<String>) -> 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(())
}
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<BKResponse>) -> 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<Url, Error> {
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<Url, Error> {
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<BKCommand> {
let (apptx, rx): (Sender<BKCommand>, Receiver<BKCommand>) = 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<BKCommand, RecvError>) -> 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
}
}
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(())
}
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<Member> = 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<Message> = 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(())
}