Removing ruma and renaming the project to Guillotine

I've simplified the project, removing all the tokio communication stuff
and the ruma-client, calling directly to the Matrix.org API using the
reqwest rust lib.
This commit is contained in:
Daniel García Moreno 2017-08-17 18:15:46 +02:00
parent d9a7369df8
commit 98ba736040
8 changed files with 281 additions and 1464 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
target
*~
*.swp
*.swo

1226
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,16 @@
[package]
authors = ["Jonas Platte <mail@jonasplatte.de>"]
name = "ruma-gtk"
authors = ["Daniel Garcia <danigm@wadobo.com>"]
name = "guillotine"
version = "0.1.0"
[dependencies]
futures = "0.1.14"
gio = "0.1.3"
hyper = "0.11.1"
tokio-core = "0.1.8"
reqwest = "0.7.2"
serde = "1.0.11"
serde_derive = "1.0.11"
serde_json = "1.0.2"
url = "1.5.1"
[dependencies.gtk]
features = ["v3_12"]
version = "0.1.3"
[dependencies.ruma-client]
git = "https://github.com/jplatte/ruma-client.git"
branch = "synapse-workarounds"

6
README.md Normal file
View file

@ -0,0 +1,6 @@
Guillotine
==========
This project is based on ruma-gtk https://github.com/jplatte/ruma-gtk
But derives in a new one using directly the matrix.org API.

View file

@ -1,29 +1,27 @@
use std::{self, env, thread};
use std::time::Duration;
extern crate gtk;
extern crate gio;
use std::env;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::channel;
use std::sync::mpsc::{Sender, Receiver};
use futures::{self, Sink};
use gio;
use gtk;
use gtk::prelude::*;
use url::Url;
use self::gtk::prelude::*;
use backend::Backend;
use backend;
use bg_thread::{self, Command, ConnectionMethod};
// TODO: Is this the correct format for GApplication IDs?
const APP_ID: &'static str = "jplatte.ruma_gtk";
const APP_ID: &'static str = "org.gnome.guillotine";
struct AppLogic {
struct AppOp {
gtk_builder: gtk::Builder,
/// Sender for the matrix channel.
///
/// This channel is used to send commands to the background thread.
command_chan_tx: futures::sink::Wait<futures::sync::mpsc::Sender<bg_thread::Command>>,
backend: Backend,
}
impl AppLogic {
impl AppOp {
pub fn login(&mut self) {
let user_entry: gtk::Entry = self.gtk_builder.get_object("login_username").unwrap();
let pass_entry: gtk::Entry = self.gtk_builder.get_object("login_password").unwrap();
@ -32,8 +30,6 @@ impl AppLogic {
let username = match user_entry.get_text() { Some(s) => s, None => String::from("") };
let password = match pass_entry.get_text() { Some(s) => s, None => String::from("") };
println!("Login: {}, {}", username, password);
self.connect(username, password, server_entry.get_text());
}
@ -43,19 +39,9 @@ impl AppLogic {
None => String::from("https://matrix.org")
};
let res = self.command_chan_tx
.send(Command::Connect {
homeserver_url: Url::parse(&server_url).unwrap(),
connection_method: ConnectionMethod::Login {
username: username,
password: password,
},
});
match res {
Ok(_) => {},
Err(error) => println!("{:?}", error)
}
self.show_loading();
self.backend.login(username, password, server_url).unwrap();
self.hide_popup();
}
pub fn connect_guest(&mut self, server: Option<String>) {
@ -64,16 +50,44 @@ impl AppLogic {
None => String::from("https://matrix.org")
};
let res = self.command_chan_tx
.send(Command::Connect {
homeserver_url: Url::parse(&server_url).unwrap(),
connection_method: ConnectionMethod::Guest
});
self.show_loading();
self.backend.guest(server_url).unwrap();
}
match res {
Ok(_) => {},
Err(error) => println!("{:?}", error)
}
pub fn get_username(&self) {
self.backend.get_username().unwrap();
}
pub fn set_username(&self, username: &str) {
self.gtk_builder
.get_object::<gtk::Label>("display_name_label")
.expect("Can't find display_name_label in ui file.")
.set_text(username);
self.show_username();
}
pub fn show_username(&self) {
self.gtk_builder
.get_object::<gtk::Stack>("user_button_stack")
.expect("Can't find user_button_stack in ui file.")
.set_visible_child_name("user_connected_page");
}
pub fn show_loading(&self) {
self.gtk_builder
.get_object::<gtk::Stack>("user_button_stack")
.expect("Can't find user_button_stack in ui file.")
.set_visible_child_name("user_loading_page");
}
pub fn hide_popup(&self) {
let user_menu: gtk::Popover = self.gtk_builder.get_object("user_menu")
.expect("Couldn't find user_menu in ui file.");
user_menu.hide();
}
pub fn disconnect(&mut self) {
println!("Disconnecting");
}
}
@ -88,18 +102,7 @@ pub struct App {
/// Used to access the UI elements.
gtk_builder: gtk::Builder,
/// Channel receiver which allows to run actions from the matrix connection thread.
///
/// Long polling is required to receive messages from the rooms and so they have to
/// run in separate threads. In order to allow those threads to modify the gtk content,
/// they will send closures to the main thread using this channel.
ui_dispatch_chan_rx: std::sync::mpsc::Receiver<Box<Fn(&gtk::Builder) + Send>>,
/// Matrix communication thread join handler used to clean up the tread when
/// closing the application.
bg_thread_join_handle: thread::JoinHandle<()>,
logic: Arc<Mutex<AppLogic>>,
op: Arc<Mutex<AppOp>>,
}
impl App {
@ -108,25 +111,37 @@ impl App {
let gtk_app = gtk::Application::new(Some(APP_ID), gio::ApplicationFlags::empty())
.expect("Failed to initialize GtkApplication");
let (tx, rx): (Sender<backend::BKResponse>, Receiver<backend::BKResponse>) = channel();
let gtk_builder = gtk::Builder::new_from_file("res/main_window.glade");
let op = Arc::new(Mutex::new(
AppOp{
gtk_builder: gtk_builder.clone(),
backend: Backend::new(tx),
}
));
let (command_chan_tx, command_chan_rx) = futures::sync::mpsc::channel(1);
let command_chan_tx = command_chan_tx.wait();
let theop = op.clone();
gtk::timeout_add(500, move || {
let recv = rx.try_recv();
match recv {
Ok(backend::BKResponse::Token(uid, _)) => {
theop.lock().unwrap().set_username(&uid);
theop.lock().unwrap().get_username();
},
Ok(backend::BKResponse::Name(username)) => {
theop.lock().unwrap().set_username(&username);
},
Err(_) => { },
};
// Create channel to allow the matrix connection thread to send closures to the main loop.
let (ui_dispatch_chan_tx, ui_dispatch_chan_rx) = std::sync::mpsc::channel();
let bg_thread_join_handle =
thread::spawn(move || bg_thread::run(command_chan_rx, ui_dispatch_chan_tx));
let logic = Arc::new(Mutex::new(AppLogic{ gtk_builder: gtk_builder.clone(), command_chan_tx }));
gtk::Continue(true)
});
let app = App {
gtk_app,
gtk_builder,
ui_dispatch_chan_rx,
bg_thread_join_handle,
logic: logic.clone(),
op: op.clone(),
};
app.connect_gtk();
@ -135,13 +150,17 @@ impl App {
pub fn connect_gtk(&self) {
let gtk_builder = self.gtk_builder.clone();
let logic = self.logic.clone();
let op = self.op.clone();
self.gtk_app.connect_activate(move |app| {
// Set up shutdown callback
let window: gtk::Window = gtk_builder.get_object("main_window")
.expect("Couldn't find main_window in ui file.");
window.set_title("Guillotine");
let op_c = op.clone();
window.connect_delete_event(clone!(app => move |_, _| {
op_c.lock().unwrap().disconnect();
app.quit();
Inhibit(false)
}));
@ -158,8 +177,8 @@ impl App {
// Login click
let login_btn: gtk::Button = gtk_builder.get_object("login_button")
.expect("Couldn't find login_button in ui file.");
let logic_c = logic.clone();
login_btn.connect_clicked(move |_| logic_c.lock().unwrap().login());
let op_c = op.clone();
login_btn.connect_clicked(move |_| op_c.lock().unwrap().login());
// Associate window with the Application and show it
window.set_application(Some(app));
@ -167,7 +186,7 @@ impl App {
});
}
pub fn run(mut self) {
pub fn run(self) {
// Convert the args to a Vec<&str>. Application::run requires argv as &[&str]
// and envd::args() returns an iterator of Strings.
let args = env::args().collect::<Vec<_>>();
@ -175,24 +194,9 @@ impl App {
// connecting as guest
// TODO: Use stored user if exists
self.logic.lock().unwrap().connect_guest(None);
// Poll the matrix communication thread channel and run the closures to allow
// the threads to run actions in the main loop.
let ui_dispatch_chan_rx = self.ui_dispatch_chan_rx;
let gtk_builder = self.gtk_builder;
gtk::idle_add(move || {
if let Ok(dispatch_fn) = ui_dispatch_chan_rx.recv_timeout(Duration::from_millis(5)) {
dispatch_fn(&gtk_builder);
}
Continue(true)
});
self.op.lock().unwrap().connect_guest(None);
// Run the main loop.
self.gtk_app.run(args_refs.len() as i32, &args_refs);
// Clean up
self.bg_thread_join_handle.join().unwrap();
}
}

175
src/backend.rs Normal file
View file

@ -0,0 +1,175 @@
extern crate url;
extern crate reqwest;
use std::sync::{Arc, Mutex};
use std::thread;
use std::collections::HashMap;
use self::url::Url;
use std::sync::mpsc::Sender;
// TODO: send errors to the frontend
macro_rules! get {
($url: expr, $attrs: expr, $resp: ident, $okcb: expr) => {
query!(get, $url, $attrs, $resp, $okcb, |err| {
println!("ERROR {:?}", err);
});
};
}
macro_rules! post {
($url: expr, $attrs: expr, $resp: ident, $okcb: expr) => {
query!(post, $url, $attrs, $resp, $okcb, |err| {
println!("ERROR {:?}", err);
});
};
}
macro_rules! query {
($method: ident, $url: expr, $attrs: expr, $resp: ident, $okcb: expr, $errcb: expr) => {
// TODO: remove unwrap and manage errors
thread::spawn(move || {
let client = reqwest::Client::new().unwrap();
let mut conn = client.$method($url.as_str()).unwrap();
let conn2 = conn.json(&$attrs).unwrap();
let mut res = conn2.send().unwrap();
let js: Result<$resp, _> = res.json();
match js {
Ok(r) => {
$okcb(r)
},
Err(err) => {
$errcb(err)
}
}
//let mut content = String::new();
//res.read_to_string(&mut content);
//cb(content);
});
};
}
#[derive(Debug)]
pub enum Error {
BackendError,
ReqwestError(reqwest::Error),
}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Error {
Error::ReqwestError(err)
}
}
impl From<url::ParseError> for Error {
fn from(_: url::ParseError) -> Error {
Error::BackendError
}
}
pub struct BackendData {
user_id: String,
access_token: String,
server_url: String,
}
pub struct Backend {
tx: Sender<BKResponse>,
data: Arc<Mutex<BackendData>>,
}
#[derive(Debug)]
pub enum BKResponse {
Token(String, String),
Name(String),
}
#[derive(Deserialize)]
#[derive(Debug)]
pub struct Response {
user_id: String,
access_token: String,
}
#[derive(Deserialize)]
#[derive(Debug)]
pub struct DisplayNameResponse {
displayname: String,
}
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"),
};
Backend { tx: tx, data: Arc::new(Mutex::new(data)) }
}
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 map: HashMap<String, String> = HashMap::new();
let data = self.data.clone();
let tx = self.tx.clone();
post!(url, map, Response,
|r: Response| {
let uid = r.user_id.clone();
let tk = r.access_token.clone();
data.lock().unwrap().user_id = uid.clone();
data.lock().unwrap().access_token = tk.clone();
tx.send(BKResponse::Token(uid, tk)).unwrap();
}
);
Ok(())
}
pub fn login(&self, user: String, password: String, server: String) -> Result<(), Error> {
let s = server.clone();
let url = Url::parse(&s)?.join("/_matrix/client/r0/login")?;
self.data.lock().unwrap().server_url = s;
let mut map = HashMap::new();
map.insert("type", String::from("m.login.password"));
map.insert("user", user);
map.insert("password", password);
let data = self.data.clone();
let tx = self.tx.clone();
post!(url, map, Response,
|r: Response| {
let uid = r.user_id.clone();
let tk = r.access_token.clone();
data.lock().unwrap().user_id = uid.clone();
data.lock().unwrap().access_token = tk.clone();
tx.send(BKResponse::Token(uid, tk)).unwrap();
}
);
Ok(())
}
pub fn get_username(&self) -> Result<(), Error> {
let s = self.data.lock().unwrap().server_url.clone();
let id = self.data.lock().unwrap().user_id.clone() + "/";
let url = Url::parse(&s)?.join("/_matrix/client/r0/profile/")?.join(&id)?.join("displayname")?;
let map: HashMap<String, String> = HashMap::new();
let tx = self.tx.clone();
get!(url, map, DisplayNameResponse,
|r: DisplayNameResponse| {
tx.send(BKResponse::Name(r.displayname.clone())).unwrap();
}
);
Ok(())
}
}

View file

@ -1,121 +0,0 @@
use std;
use futures;
use futures::future::{self, Loop, Future};
use futures::stream::Stream;
use gtk;
use ruma_client::{self, Client as RumaClient};
use tokio_core::reactor::{Core as TokioCore, Handle as TokioHandle};
use url::Url;
pub enum Command {
Connect {
homeserver_url: Url,
connection_method: ConnectionMethod,
},
}
#[derive(Clone)]
pub enum ConnectionMethod {
Login { username: String, password: String },
Guest,
//Register,
}
#[derive(Debug)]
enum Error {
RumaClientError(ruma_client::Error),
RecvError(std::sync::mpsc::RecvError),
}
impl From<ruma_client::Error> for Error {
fn from(err: ruma_client::Error) -> Error {
Error::RumaClientError(err)
}
}
impl From<std::sync::mpsc::RecvError> for Error {
fn from(err: std::sync::mpsc::RecvError) -> Error {
Error::RecvError(err)
}
}
fn bg_main<'a>(
tokio_handle: &'a TokioHandle,
command_chan_rx: futures::sync::mpsc::Receiver<Command>,
ui_dispatch_chan_tx: std::sync::mpsc::Sender<Box<Fn(&gtk::Builder) + Send>>,
) -> impl Future<Item = (), Error = Error> + 'a {
future::loop_fn(command_chan_rx, move |command_chan_rx| {
command_chan_rx
.into_future()
// Some sort of error occurred that is not the channel being closed?! Error type is (),
// so it doesn't even impl Error. Assume this will never happen (for now).
.map_err(|_| unreachable!())
.and_then(|(opt_command, command_chan_rx)| match opt_command {
Some(command) => {
let (homeserver_url, connection_method) = match command {
Command::Connect { homeserver_url, connection_method }
=> (homeserver_url, connection_method),
//_ => unimplemented!(),
};
Ok((homeserver_url, connection_method, command_chan_rx))
}
None => Err(std::sync::mpsc::RecvError.into()),
}).and_then(move |(homeserver_url, connection_method, command_chan_rx)| {
let client = RumaClient::https(tokio_handle, homeserver_url, None).unwrap();
match connection_method {
ConnectionMethod::Login { username, password } => {
future::Either::A(client.log_in(username, password))
}
ConnectionMethod::Guest => future::Either::B(client.register_guest()),
}.and_then(move |_| {
future::loop_fn((), move |_| {
use ruma_client::api::r0::sync::sync_events;
sync_events::call(client.clone(), sync_events::Request {
filter: None,
since: None,
full_state: None,
set_presence: None,
timeout: None,
}).map(|res| {
println!("{:?}", res);
Loop::Continue(())
})
})
}).map_err(Error::from)
//.select(command_chan_rx.into_future())
})
})
/*ui_dispatch_chan_tx.send(box move |builder| {
builder
.get_object::<gtk::Stack>("user_button_stack")
.expect("Can't find user_button_stack in ui file.")
.set_visible_child_name("user_connected_page");
builder
.get_object::<gtk::Label>("display_name_label")
.expect("Can't find display_name_label in ui file.")
.set_text("Guest");
});*/
}
pub fn run(
command_chan_rx: futures::sync::mpsc::Receiver<Command>,
ui_dispatch_chan_tx: std::sync::mpsc::Sender<Box<Fn(&gtk::Builder) + Send>>,
) {
let mut core = TokioCore::new().unwrap();
let tokio_handle = core.handle();
match core.run(bg_main(&tokio_handle, command_chan_rx, ui_dispatch_chan_tx)) {
Ok(_) => {}
Err(e) => {
// TODO: Show error message in UI. Quit / restart thread?
eprintln!("ruma_gtk: background thread error: {:?}", e);
}
};
}

View file

@ -1,36 +1,16 @@
#![feature(box_syntax)]
#![feature(conservative_impl_trait)]
// not using this yet because rustfmt doesn't support it:
// https://github.com/rust-lang-nursery/rustfmt/issues/1215
//#![feature(field_init_shorthand)]
extern crate futures;
extern crate hyper;
extern crate gio;
extern crate gtk;
extern crate ruma_client;
extern crate tokio_core;
extern crate url;
// extern crate xdg;
#[macro_use]
extern crate serde_derive;
#[macro_use]
mod util;
mod backend;
mod app;
mod bg_thread;
use app::App;
// use std::fs::File;
// use std::path::Path;
fn main() {
// let xdg_dirs = xdg::BaseDirectories::with_prefix("ruma_gtk").unwrap();
// let data_path = xdg_dirs.place_data_file("data.yml").unwrap();
// TODO: Read settings
let app = App::new();
app.run();
// TODO: Save settings
}