Commit 98ba7360 authored by Daniel García Moreno's avatar Daniel García Moreno

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.
parent d9a7369d
target
*~
*.swp
*.swo
This diff is collapsed.
[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"
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.
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 self::gtk::prelude::*;
use futures::{self, Sink};
use gio;
use gtk;
use gtk::prelude::*;
use url::Url;
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();
}
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();
}
match res {
Ok(_) => {},
Err(error) => println!("{:?}", error)
}
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 gtk_builder = gtk::Builder::new_from_file("res/main_window.glade");
let (command_chan_tx, command_chan_rx) = futures::sync::mpsc::channel(1);
let command_chan_tx = command_chan_tx.wait();
// 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 (tx, rx): (Sender<backend::BKResponse>, Receiver<backend::BKResponse>) = channel();
let bg_thread_join_handle =
thread::spawn(move || bg_thread::run(command_chan_rx, ui_dispatch_chan_tx));
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 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(_) => { },
};
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();
}
}
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(())
}
}
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);
}
};
}
#![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
fn main() {
let app = App::new();
app.run();
// TODO: Save settings
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment