├── client-gtk ├── .gitignore ├── Cargo.toml ├── src │ ├── messages.rs │ ├── connections.rs │ └── main.rs └── Cargo.lock ├── .gitignore ├── common ├── .gitignore ├── README ├── Cargo.toml └── src │ └── lib.rs ├── client ├── .gitignore ├── Cargo.toml ├── src │ ├── parser.rs │ ├── help.rs │ ├── connect.rs │ ├── listener.rs │ ├── frontend.rs │ └── main.rs └── Cargo.lock ├── server ├── .gitignore ├── gencert.sh ├── Cargo.toml └── Cargo.lock └── README.md /client-gtk/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | synac.wiki 2 | synac-rs 3 | -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | data.sqlite 3 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | data.sqlite 3 | cert.pfx 4 | optional-config.json 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LEGACY ARCHIVE 2 | 3 | This is the archive before the recent split-up (each subdirectory is now it's own repository). 4 | It's here in case anything with the split-up went wrong. 5 | 6 | DO. NOT. USE. 7 | -------------------------------------------------------------------------------- /common/README: -------------------------------------------------------------------------------- 1 | Here are some common files across both the official server and the client. 2 | 3 | It might be useful to copy paste stuff here into a client library, 4 | although it's not specifically written to be in one. 5 | -------------------------------------------------------------------------------- /server/gencert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes 4 | openssl pkcs12 -export -inkey key.pem -in cert.pem -out cert.pfx -nodes -passout pass: 5 | rm key.pem cert.pem 6 | -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client" 3 | version = "0.3.6" 4 | authors = ["jD91mZM2 "] 5 | 6 | [dependencies] 7 | openssl = "0.9" 8 | rusqlite = "0.13" 9 | rustyline = "1.0" 10 | synac = "0.1" 11 | termion = "1.5" 12 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | version = "0.1.0" 4 | authors = ["jD91mZM2 "] 5 | 6 | [dependencies] 7 | failure = "0.1.1" 8 | rmp-serde = "0.13" 9 | serde = "1.0" 10 | serde_derive = "1.0" 11 | -------------------------------------------------------------------------------- /client-gtk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client-gtk" 3 | version = "0.1.0" 4 | authors = ["jD91mZM2 "] 5 | 6 | [dependencies] 7 | chrono = "0.4" 8 | failure = "0.1.1" 9 | gtk = { version = "0.3", features = ["v3_22"] } 10 | rusqlite = "0.13" 11 | synac = "0.2" 12 | xdg = "2.1" 13 | -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server" 3 | version = "0.2.0" 4 | authors = ["jD91mZM2 "] 5 | 6 | [dependencies] 7 | bcrypt = "0.1" 8 | chrono = "0.4" 9 | futures = "0.1" 10 | openssl = "0.9" 11 | rusqlite = "0.12" 12 | serde = "1.0" 13 | serde_derive = "1.0" 14 | serde_json = "1.0" 15 | tokio-core = "0.1" 16 | tokio-io = "0.1" 17 | tokio-openssl = "0.1" 18 | common = { path = "../common" } 19 | -------------------------------------------------------------------------------- /client/src/parser.rs: -------------------------------------------------------------------------------- 1 | pub fn parse(input: &str) -> Vec { 2 | let mut parts = Vec::new(); 3 | let mut buffer = String::new(); 4 | let mut escape = false; 5 | let mut quote = false; 6 | 7 | for c in input.chars() { 8 | if escape { 9 | escape = false; 10 | if c != '\\' && c != '"' { 11 | buffer.push('\\'); 12 | } 13 | buffer.push(c); 14 | } else { 15 | match c { 16 | '\\' => escape = true, 17 | '"' if buffer.is_empty() || quote => quote = !quote, 18 | ' ' if !quote => { 19 | if !buffer.is_empty() { 20 | parts.push(buffer); 21 | buffer = String::new(); 22 | } 23 | }, 24 | c => buffer.push(c) 25 | } 26 | } 27 | } 28 | 29 | if escape { buffer.push('\\'); } 30 | if !buffer.is_empty() { parts.push(buffer); } 31 | 32 | parts 33 | } 34 | 35 | #[cfg(test)] 36 | #[test] 37 | fn test() { 38 | assert_eq!(parse(r#"hello world"#), &["hello", "world"]); 39 | assert_eq!(parse(r#""hello world""#), &["hello world"]); 40 | assert_eq!(parse(r#"hel"lo wor"ld"#), &["hel\"lo", "wor\"ld"]); 41 | assert_eq!(parse(r#"hello\ world"#), &["hello\\ world"]); 42 | assert_eq!(parse(r#"\h\e\l\l\o world"#), &["\\h\\e\\l\\l\\o", "world"]); 43 | assert_eq!(parse(r#"\"hello world\""#), &["\"hello", "world\""]); 44 | assert_eq!(parse(r#"\\\"hello world\\\""#), &["\\\"hello", "world\\\""]); 45 | } 46 | -------------------------------------------------------------------------------- /client-gtk/src/messages.rs: -------------------------------------------------------------------------------- 1 | use chrono::{Datelike, Local, Timelike, TimeZone, Utc}; 2 | use std::collections::HashMap; 3 | use std::fmt::Write; 4 | use synac::common::Message; 5 | 6 | pub struct Messages { 7 | messages: HashMap> 8 | } 9 | impl Messages { 10 | pub fn new() -> Self { 11 | Messages { 12 | messages: HashMap::new() 13 | } 14 | } 15 | pub fn add(&mut self, msg: Message) { 16 | let messages = self.messages.entry(msg.channel).or_insert_with(Vec::default); 17 | let i = match messages.binary_search_by_key(&msg.timestamp, |msg| msg.timestamp) { 18 | Err(i) => i, 19 | Ok(mut i) => { 20 | let original_timestamp = Some(messages[i].timestamp); 21 | let original_id = Some(messages[i].id); 22 | while messages.get(i-1).map(|msg| msg.timestamp) == original_timestamp { 23 | i -= 1; 24 | } 25 | loop { 26 | let message = messages.get_mut(i); 27 | if message.as_ref().map(|msg| msg.id) == original_id { 28 | *message.unwrap() = msg; 29 | return; 30 | } 31 | if message.map(|msg| msg.timestamp) != original_timestamp { 32 | break; 33 | } 34 | i += 1 35 | } 36 | i+1 37 | } 38 | }; 39 | messages.insert(i, msg); 40 | } 41 | pub fn remove(&mut self, id: usize) { 42 | for messages in self.messages.values_mut() { 43 | if let Some(i) = messages.iter().position(|msg| msg.id == id) { 44 | messages.remove(i); 45 | } 46 | } 47 | } 48 | pub fn get(&self, channel: usize) -> &[Message] { 49 | self.messages.get(&channel).map(|inner| &*inner as &[Message]).unwrap_or(&[]) 50 | } 51 | pub fn has(&self, channel: usize) -> bool { 52 | self.messages.contains_key(&channel) 53 | } 54 | } 55 | 56 | pub fn format(output: &mut String, timestamp: i64) { 57 | let time = Utc.timestamp(timestamp, 0); 58 | let local = time.with_timezone(&Local); 59 | let now = Local::now(); 60 | let diff = now.signed_duration_since(local); 61 | 62 | match diff.num_days() { 63 | 0 => output.push_str("Today"), 64 | 1 => output.push_str("Yesterday"), 65 | 2 => output.push_str("Two days ago"), 66 | 3 => output.push_str("Three days ago"), 67 | x if x < 7 => output.push_str("A few days ago"), 68 | 7 => output.push_str("A week ago"), 69 | _ => { 70 | write!(output, "{}-{}-{}", local.year(), local.month(), local.day()).unwrap(); 71 | } 72 | } 73 | output.push_str(" at "); 74 | let (is_pm, hour) = local.hour12(); 75 | write!(output, "{}:{:02} ", hour, local.minute()).unwrap(); 76 | output.push_str(if is_pm { "PM" } else { "AM" }); 77 | } 78 | -------------------------------------------------------------------------------- /client/src/help.rs: -------------------------------------------------------------------------------- 1 | pub fn help(query: &[&str], screen: &::frontend::Screen) { 2 | let all = query.is_empty(); 3 | 4 | if all || query.contains(&"ban") || query.contains(&"unban") { 5 | screen.log("\ 6 | ban/unban \n\ 7 | A ban prevents logging in as and prevents creation of accounts on their IP.\ 8 | ".to_string()); 9 | } 10 | if all || query.contains(&"connect") { 11 | let mut text = String::from("\ 12 | connect \n\ 13 | Connects to IP. Default port is \ 14 | "); 15 | text.push_str(&::common::DEFAULT_PORT.to_string()); 16 | text.push('.'); 17 | screen.log(text); 18 | } 19 | if all || query.contains(&"create") { 20 | screen.log("\ 21 | create <\"channel\"/\"group\"> [data]\n\ 22 | Creates a channel/group with .\n\ 23 | If it's a group, it may optionally take [data] as a permission string\n\ 24 | to prevent having to edit it later.\ 25 | ".to_string()); 26 | } 27 | if all || query.contains(&"delete") { 28 | screen.log("\ 29 | delete <\"channel\"/\"group\"/\"message\"> \n\ 30 | Delets channel/group with .\ 31 | ".to_string()); 32 | } 33 | if all || query.contains(&"disconnect") { 34 | screen.log("\ 35 | disconnect\n\ 36 | Disconnects from the currently connected server.\ 37 | ".to_string()); 38 | } 39 | if all || query.contains(&"forget") { 40 | screen.log("\ 41 | forget \n\ 42 | Forgets all data about the server on .\n\ 43 | Useful if the server was reset.\ 44 | ".to_string()); 45 | } 46 | if all || query.contains(&"help") { 47 | screen.log("\ 48 | help [command1 [command2 [etc...]]]\n\ 49 | Prints help about one or more commands.\n\ 50 | If left empty, it shows all of them.\ 51 | ".to_string()); 52 | } 53 | if all || query.contains(&"info") { 54 | screen.log("\ 55 | info \n\ 56 | Prints info about based on name.\n\ 57 | Useful for getting the ID for functions that require such.\ 58 | ".to_string()); 59 | } 60 | if all || query.contains(&"join") { 61 | screen.log("\ 62 | join \n\ 63 | Joins and prints out recent messages.\ 64 | ".to_string()); 65 | } 66 | if all || query.contains(&"list") { 67 | screen.log("\ 68 | list <\"channels\"/\"groups\"/\"users\">\n\ 69 | Lists all <\"channels\"/\"groups\"/\"users\">.\ 70 | ".to_string()); 71 | } 72 | if all || query.contains(&"msg") { 73 | screen.log("\ 74 | msg \n\ 75 | Sends in encrypted form privately to .\n\ 76 | See /setupkeys.\ 77 | ".to_string()); 78 | } 79 | if all || query.contains(&"nick") { 80 | screen.log("\ 81 | nick \n\ 82 | Changes the nickname to .\n\ 83 | If you are connected to a server, it also changes nickname on that server.\n\ 84 | It does *not* update all of your servers.\ 85 | ".to_string()); 86 | } 87 | if all || query.contains(&"passwd") { 88 | screen.log("\ 89 | passwd\n\ 90 | Changes password on the current server.\ 91 | ".to_string()); 92 | } 93 | if all || query.contains(&"quit") { 94 | screen.log("\ 95 | quit\n\ 96 | Quits the application.\ 97 | ".to_string()); 98 | } 99 | if all || query.contains(&"setupkeys") { 100 | screen.log("\ 101 | setupkeys \n\ 102 | Prepares for encrypted messaging with /msg.\ 103 | ".to_string()); 104 | } 105 | if all || query.contains(&"update") { 106 | screen.log("\ 107 | update <\"channel\"/\"group\"> \n\ 108 | Interactively edits <\"channel\"/\"group\"> with .\ 109 | ".to_string()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /client-gtk/src/connections.rs: -------------------------------------------------------------------------------- 1 | use failure::Error; 2 | use messages::Messages; 3 | use rusqlite::Connection as SqlConnection; 4 | use std::collections::HashMap; 5 | use std::mem; 6 | use std::net::SocketAddr; 7 | use std::rc::Rc; 8 | use std::sync::{Arc, Mutex, RwLock}; 9 | use std::thread::{self, JoinHandle}; 10 | use synac::common::{self, Packet}; 11 | use synac::{Listener, Session, State}; 12 | 13 | #[derive(Debug, Fail)] 14 | pub enum ConnectionError { 15 | #[fail(display = "invalid packet")] 16 | InvalidPacket, 17 | #[fail(display = "invalid token: password authentication needed")] 18 | InvalidToken, 19 | #[fail(display = "invalid password")] 20 | InvalidPassword 21 | } 22 | 23 | pub struct Synac { 24 | pub addr: SocketAddr, 25 | pub session: Session, 26 | pub listener: Listener, 27 | pub state: State, 28 | 29 | pub channel: Option, 30 | pub messages: Messages 31 | } 32 | impl Synac { 33 | pub fn new(addr: SocketAddr, session: Session) -> Self { 34 | Synac { 35 | addr: addr, 36 | listener: Listener::new(), 37 | session: session, 38 | state: State::new(), 39 | 40 | channel: None, 41 | messages: Messages::new() 42 | } 43 | } 44 | } 45 | 46 | pub enum Connection { 47 | Connecting(JoinHandle>), 48 | Connected(Result) 49 | } 50 | impl Connection { 51 | pub fn join(&mut self) -> Result<&mut Synac, &mut Error> { 52 | let old = mem::replace(self, unsafe { mem::uninitialized() }); 53 | let new = match old { 54 | Connection::Connecting(handle) => Connection::Connected(handle.join().unwrap()), 55 | old @ Connection::Connected(_) => old 56 | }; 57 | mem::forget(mem::replace(self, new)); 58 | 59 | match *self { 60 | Connection::Connecting(_) => unreachable!(), 61 | Connection::Connected(ref mut inner) => inner.as_mut() 62 | } 63 | } 64 | } 65 | 66 | pub struct Connections { 67 | pub current_server: Mutex>, 68 | pub nick: RwLock, 69 | pub servers: Arc>> 70 | } 71 | impl Connections { 72 | pub fn new(db: &SqlConnection, nick: String) -> Arc { 73 | let me = Arc::new(Connections { 74 | current_server: Mutex::new(None), 75 | nick: RwLock::new(nick), 76 | servers: Arc::new(Mutex::new(HashMap::new())) 77 | }); 78 | { 79 | let mut servers = me.servers.lock().unwrap(); 80 | 81 | let mut stmt = db.prepare("SELECT ip, hash, token FROM servers").unwrap(); 82 | let mut rows = stmt.query(&[]).unwrap(); 83 | 84 | while let Some(row) = rows.next() { 85 | let row = row.unwrap(); 86 | let addr = match parse_addr(&row.get::<_, String>(0)) { 87 | Some(addr) => addr, 88 | None => { 89 | eprintln!("invalid socket address, skipping"); 90 | continue; 91 | } 92 | }; 93 | let hash = row.get(1); 94 | let token = row.get(2); 95 | 96 | let me_clone = Arc::clone(&me); 97 | servers.insert(addr, Connection::Connecting(thread::spawn(move || { 98 | me_clone.connect(addr, hash, token, || None) 99 | .map(|session| Synac::new(addr, session)) 100 | .map_err(|err| { eprintln!("connect error: {}", err); err }) 101 | }))); 102 | } 103 | } 104 | 105 | me 106 | } 107 | pub fn connect(&self, addr: SocketAddr, hash: String, token: Option, password: F) 108 | -> Result 109 | where F: FnOnce() -> Option<(String, Rc)> 110 | { 111 | let mut session = Session::new(addr, hash)?; 112 | 113 | if let Some(token) = token { 114 | session.login_with_token(false, self.nick.read().unwrap().clone(), token)?; 115 | match session.read()? { 116 | Packet::LoginSuccess(_) => { 117 | session.set_nonblocking(true)?; 118 | return Ok(session); 119 | }, 120 | Packet::Err(common::ERR_LOGIN_INVALID) => {}, 121 | _ => return Err(ConnectionError::InvalidPacket.into()) 122 | } 123 | } 124 | if let Some((password, db)) = password() { 125 | session.login_with_password(false, self.nick.read().unwrap().clone(), password)?; 126 | match session.read()? { 127 | Packet::LoginSuccess(login) => { 128 | db.execute("UPDATE servers SET token = ? WHERE ip = ?", &[&login.token, &addr.to_string()]).unwrap(); 129 | session.set_nonblocking(true)?; 130 | return Ok(session); 131 | }, 132 | Packet::Err(common::ERR_LOGIN_INVALID) => 133 | return Err(ConnectionError::InvalidPassword.into()), 134 | _ => return Err(ConnectionError::InvalidPacket.into()) 135 | } 136 | } 137 | 138 | Err(ConnectionError::InvalidToken.into()) 139 | } 140 | pub fn insert(&self, addr: SocketAddr, result: Synac) { 141 | self.servers.lock().unwrap() 142 | .insert(addr, Connection::Connected(Ok(result))); 143 | } 144 | pub fn set_current(&self, addr: Option) { 145 | *self.current_server.lock().unwrap() = addr; 146 | } 147 | pub fn execute(&self, addr: SocketAddr, callback: F) 148 | where F: FnOnce(Result<&mut Synac, &mut Error>) 149 | { 150 | let mut servers = self.servers.lock().unwrap(); 151 | let server = servers.get_mut(&addr); 152 | 153 | if let Some(inner) = server { 154 | callback(inner.join()); 155 | } 156 | } 157 | pub fn try_read(&self, mut callback: F) -> Result<(), Error> 158 | where F: FnMut(&mut Synac, Packet) 159 | { 160 | if let Ok(mut servers) = self.servers.try_lock() { 161 | for server in servers.values_mut() { 162 | if let Ok(ref mut synac) = server.join() { 163 | let read = synac.listener.try_read(synac.session.inner_stream())?; 164 | if let Some(packet) = read { 165 | synac.state.update(&packet); 166 | if let Packet::MessageReceive(ref event) = packet { 167 | synac.messages.add(event.inner.clone()); 168 | } else if let Packet::MessageDeleteReceive(ref msg) = packet { 169 | synac.messages.remove(msg.id); 170 | } 171 | callback(synac, packet); 172 | } 173 | } 174 | } 175 | } 176 | Ok(()) 177 | } 178 | } 179 | 180 | pub fn parse_addr(input: &str) -> Option { 181 | let mut parts = input.rsplitn(2, ':'); 182 | let addr = match (parts.next()?, parts.next()) { 183 | (port, Some(addr)) => (addr, port.parse().ok()?), 184 | (addr, None) => (addr, common::DEFAULT_PORT) 185 | }; 186 | 187 | use std::net::ToSocketAddrs; 188 | addr.to_socket_addrs() 189 | .ok() 190 | .and_then(|mut addrs| addrs.next()) 191 | } 192 | -------------------------------------------------------------------------------- /client/src/connect.rs: -------------------------------------------------------------------------------- 1 | use *; 2 | use synac::common::Packet; 3 | 4 | use frontend; 5 | 6 | pub fn connect( 7 | addr: SocketAddr, 8 | connector: &Connector, 9 | screen: &frontend::Screen 10 | ) -> Option { 11 | // See https://github.com/rust-lang/rust/issues/35853 12 | macro_rules! println { 13 | () => { screen.log(String::new()); }; 14 | ($arg:expr) => { screen.log(String::from($arg)); }; 15 | ($($arg:expr),*) => { screen.log(format!($($arg),*)); }; 16 | } 17 | macro_rules! readline { 18 | ($break:block) => { 19 | match screen.readline() { 20 | Ok(ok) => ok, 21 | Err(_) => $break 22 | } 23 | } 24 | } 25 | macro_rules! readpass { 26 | ($break:block) => { 27 | match screen.readpass() { 28 | Ok(ok) => ok, 29 | Err(_) => $break 30 | } 31 | } 32 | } 33 | 34 | let db = connector.db.lock().unwrap(); 35 | let nick = connector.nick.read().unwrap(); 36 | 37 | let mut stmt = db.prepare("SELECT key, token FROM servers WHERE ip = ?").unwrap(); 38 | let mut rows = stmt.query(&[&addr.to_string()]).unwrap(); 39 | 40 | let public_key: String; 41 | let mut token: Option = None; 42 | if let Some(row) = rows.next() { 43 | let row = row.unwrap(); 44 | public_key = row.get(0); 45 | token = row.get(1); 46 | } else { 47 | println!("To securely connect, data from the server (\"public key\") is needed."); 48 | println!("You can obtain the \"public key\" from the server owner."); 49 | println!("Enter the key here:"); 50 | public_key = readline!({ return None; }); 51 | 52 | db.execute( 53 | "INSERT INTO servers (ip, key) VALUES (?, ?)", 54 | &[&addr.to_string(), &public_key] 55 | ).unwrap(); 56 | } 57 | let mut inner = match synac::Session::new(addr, public_key) { 58 | Ok(ok) => ok, 59 | Err(err) => { 60 | println!("Failed to open session: {}", err); 61 | return None; 62 | } 63 | }; 64 | 65 | let mut id = None; 66 | if let Some(token) = token { 67 | if let Err(err) = inner.login_with_token(false, nick.to_string(), token.to_string()) { 68 | println!("Could not request login"); 69 | println!("{}", err); 70 | return None; 71 | } 72 | 73 | match inner.read() { 74 | Ok(Packet::LoginSuccess(login)) => { 75 | id = Some(login.id); 76 | if login.created { 77 | println!("Tried to log in with your token: Apparently an account was created."); 78 | println!("I think you should stay away from this server. It's weird."); 79 | return None; 80 | } 81 | println!("Logged in as user #{}", login.id); 82 | }, 83 | Ok(Packet::Err(code)) => match code { 84 | common::ERR_LOGIN_INVALID | 85 | common::ERR_MISSING_FIELD => {}, 86 | common::ERR_LIMIT_REACHED => { 87 | println!("Username too long"); 88 | return None; 89 | }, 90 | common::ERR_LOGIN_BANNED => { 91 | println!("You have been banned from this server. :("); 92 | return None; 93 | }, 94 | common::ERR_LOGIN_BOT => { 95 | println!("This account is a bot account"); 96 | return None; 97 | }, 98 | common::ERR_MAX_CONN_PER_IP => { 99 | println!("Too many connections made from this IP"); 100 | return None; 101 | }, 102 | _ => { 103 | println!("The server responded with an invalid error."); 104 | return None; 105 | } 106 | }, 107 | Ok(_) => { 108 | println!("The server responded with an invalid packet."); 109 | return None; 110 | } 111 | Err(err) => { 112 | println!("Failed to read from server"); 113 | println!("{}", err); 114 | return None; 115 | } 116 | } 117 | } 118 | 119 | if id.is_none() { 120 | println!("If you don't have an account, choose a new password here."); 121 | println!("Otherwise, enter your existing one."); 122 | println!("Password: "); 123 | let pass = readpass!({ return None; }); 124 | 125 | if let Err(err) = inner.login_with_password(false, nick.to_string(), pass) { 126 | println!("Could not request login"); 127 | println!("{}", err); 128 | return None; 129 | } 130 | 131 | match inner.read() { 132 | Ok(Packet::LoginSuccess(login)) => { 133 | db.execute( 134 | "UPDATE servers SET token = ? WHERE ip = ?", 135 | &[&login.token, &addr.to_string()] 136 | ).unwrap(); 137 | if login.created { 138 | println!("Account created"); 139 | } 140 | id = Some(login.id); 141 | println!("Logged in as user #{}", login.id); 142 | }, 143 | Ok(Packet::Err(code)) => match code { 144 | common::ERR_LIMIT_REACHED => { 145 | println!("Username too long"); 146 | return None; 147 | }, 148 | common::ERR_LOGIN_BANNED => { 149 | println!("You have been banned from this server. :("); 150 | return None; 151 | }, 152 | common::ERR_LOGIN_BOT => { 153 | println!("This account is a bot account"); 154 | return None; 155 | }, 156 | common::ERR_LOGIN_INVALID => { 157 | println!("Invalid credentials"); 158 | return None; 159 | }, 160 | _ => { 161 | println!("The server responded with an invalid error. :/"); 162 | return None; 163 | } 164 | }, 165 | Ok(_) => { 166 | println!("The server responded with an invalid packet. :/"); 167 | return None; 168 | } 169 | Err(err) => { 170 | println!("Failed to read from server"); 171 | println!("{}", err); 172 | return None; 173 | } 174 | } 175 | } 176 | inner.inner_stream().get_ref().set_nonblocking(true).expect("Failed to make stream non-blocking"); 177 | Some(Session::new(addr, id.unwrap(), inner)) 178 | } 179 | 180 | pub fn reconnect( 181 | connector: &Connector, 182 | err: &std::io::Error, 183 | screen: &frontend::Screen, 184 | session: &mut Session 185 | ) { 186 | if err.kind() == std::io::ErrorKind::BrokenPipe { 187 | screen.log(String::from("Attempting reconnect...")); 188 | if let Some(new) = connect(session.addr, &connector, screen) { 189 | *session = new; 190 | } 191 | } 192 | } 193 | 194 | pub fn write( 195 | connector: &Connector, 196 | packet: &Packet, 197 | screen: &frontend::Screen, 198 | session: &mut Session 199 | ) -> bool { 200 | if let Err(err) = session.inner.send(packet) { 201 | screen.log(String::from("Sending failed.")); 202 | screen.log(format!("{}", err)); 203 | if let synac::Error::IoError(err) = err { 204 | reconnect(&connector, &err, screen, session); 205 | } 206 | return false; 207 | } 208 | true 209 | } 210 | -------------------------------------------------------------------------------- /client/src/listener.rs: -------------------------------------------------------------------------------- 1 | use *; 2 | use synac::Listener; 3 | use synac::common::Packet; 4 | use rusqlite::Connection as SqlConnection; 5 | use std::sync::mpsc; 6 | use std::sync::{Arc, Mutex}; 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | use frontend; 11 | 12 | pub fn listen( 13 | db: Arc>, 14 | screen: Arc, 15 | tx_sent: mpsc::SyncSender<()>, 16 | session: Arc>>, 17 | rx_stop: mpsc::Receiver<()> 18 | ) { 19 | macro_rules! println { 20 | () => { screen.log(String::new()); }; 21 | ($arg:expr) => { screen.log(String::from($arg)); }; 22 | ($($arg:expr),*) => { screen.log(format!($($arg),*)); }; 23 | } 24 | 25 | let mut typing_last = Instant::now(); 26 | let typing_check = Duration::from_secs(1); 27 | 28 | let mut listener = Listener::new(); 29 | 30 | loop { 31 | thread::sleep(Duration::from_millis(1)); 32 | 33 | match rx_stop.try_recv() { 34 | Ok(_) | 35 | Err(mpsc::TryRecvError::Disconnected) => break, 36 | _ => {} 37 | } 38 | 39 | if let Some(ref mut session) = *session.lock().unwrap() { 40 | if typing_last.elapsed() >= typing_check { 41 | typing_last = Instant::now(); 42 | let duration = Duration::from_secs(common::TYPING_TIMEOUT as u64); // TODO use const fn 43 | session.typing.retain(|_, time| time.elapsed() < duration); 44 | 45 | let people = session.typing.keys() 46 | .filter_map(|&(author, channel)| { 47 | if Some(channel) != session.channel { 48 | return None; 49 | } 50 | 51 | session.state.users.get(&author).map(|user| &user.name) 52 | }); 53 | 54 | screen.typing_set(get_typing_string(people, session.typing.len())); 55 | } 56 | let packet = listener.try_read(session.inner.inner_stream()); 57 | if let Err(err) = packet { 58 | println!("Error receiving packet: {}", err); 59 | continue; 60 | } 61 | if let Some(packet) = packet.unwrap() { 62 | screen.delete(LogEntryId::Sending); 63 | session.state.update(&packet); 64 | 65 | match packet { 66 | Packet::LoginSuccess(event) => { 67 | db.lock().unwrap().execute( 68 | "UPDATE servers SET token = ? WHERE ip = ?", 69 | &[&event.token, &session.addr.to_string()] 70 | ).unwrap(); 71 | }, 72 | Packet::MessageDeleteReceive(event) => { 73 | screen.delete(LogEntryId::Message(event.id)); 74 | screen.repaint(); 75 | }, 76 | Packet::MessageReceive(msg) => { 77 | let msg = msg.inner; 78 | session.typing.remove(&(msg.author, msg.channel)); 79 | 80 | if let Some(user) = session.state.users.get(&msg.author) { 81 | if session.channel == Some(msg.channel) { 82 | screen.log_with_id( 83 | format!( 84 | "{} (ID #{}): {}", 85 | user.name, 86 | msg.id, 87 | frontend::sanitize( 88 | String::from_utf8_lossy(&msg.text) 89 | .into_owned() 90 | ) 91 | ), 92 | LogEntryId::Message(msg.id) 93 | ); 94 | } 95 | if msg.author == session.id { 96 | session.last = Some((msg.id, msg.text)); 97 | } 98 | } 99 | }, 100 | Packet::PMReceive(msg) => { 101 | let db = db.lock().unwrap(); 102 | let mut stmt = db.prepare_cached("SELECT private FROM pms WHERE recipient = ?") 103 | .unwrap(); 104 | let mut rows = stmt.query(&[&(msg.author as i64)]).unwrap(); 105 | 106 | if let Some(row) = rows.next() { 107 | let row = row.unwrap(); 108 | 109 | use openssl::rsa::Rsa; 110 | match Rsa::private_key_from_pem(&row.get::<_, Vec>(0)) { 111 | Ok(rsa) => { 112 | if let Ok(decrypted) = synac::decrypt(&msg.text, &rsa) { 113 | let user = session.state.users.get(&msg.author) 114 | .map(|user| &*user.name) 115 | .unwrap_or("unknown"); 116 | println!( 117 | "{} privately messaged you: {}", 118 | user, 119 | String::from_utf8_lossy(&decrypted) 120 | ); 121 | } 122 | }, 123 | Err(err) => { 124 | println!("Failed to deserialize PEM."); 125 | println!("Did you edit the SQLite database?"); 126 | println!("Details: {}", err); 127 | } 128 | } 129 | } 130 | } 131 | Packet::RateLimited(time) => { 132 | println!("Slow down! You may try again in {} seconds.", time); 133 | }, 134 | Packet::TypingReceive(event) => { 135 | if event.author != session.id { 136 | session.typing.insert((event.author, event.channel), Instant::now()); 137 | } 138 | }, 139 | Packet::Err(common::ERR_GROUP_INVALID_POS) => { 140 | println!("Invalid group position"); 141 | }, 142 | Packet::Err(common::ERR_GROUP_LOCKED_NAME) => { 143 | println!("Can not change the name of that group"); 144 | }, 145 | Packet::Err(common::ERR_LIMIT_REACHED) => { 146 | println!("Too short or too long. No idea which"); 147 | }, 148 | Packet::Err(common::ERR_LOGIN_INVALID) => { 149 | println!("Invalid credentials"); 150 | }, 151 | Packet::Err(common::ERR_MISSING_PERMISSION) => { 152 | println!("Missing permission"); 153 | }, 154 | Packet::Err(common::ERR_NAME_TAKEN) => { 155 | println!("Name is already taken") 156 | }, 157 | Packet::Err(common::ERR_UNKNOWN_CHANNEL) => { 158 | println!("This channel was deleted"); 159 | }, 160 | Packet::Err(common::ERR_UNKNOWN_GROUP) => { 161 | println!("This group was deleted"); 162 | }, 163 | Packet::Err(err) => { 164 | println!("Unimplemented error: {:?}", err); 165 | }, 166 | _ => {} 167 | } 168 | screen.update(&session); 169 | let _ = tx_sent.try_send(()); 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate failure; 2 | extern crate rmp_serde as rmps; 3 | #[macro_use] extern crate serde_derive; 4 | 5 | use std::collections::HashMap; 6 | use std::io; 7 | 8 | pub const DEFAULT_PORT: u16 = 8439; 9 | pub const RSA_LENGTH: u32 = 3072; 10 | pub const TYPING_TIMEOUT: u8 = 10; 11 | 12 | pub const LIMIT_USER_NAME: usize = 128; 13 | pub const LIMIT_CHANNEL_NAME: usize = 128; 14 | pub const LIMIT_GROUP_NAME: usize = 128; 15 | pub const LIMIT_GROUP_AMOUNT: usize = 2048; 16 | pub const LIMIT_MESSAGE: usize = 16384; 17 | 18 | pub const LIMIT_BULK: usize = 64; 19 | 20 | pub const ERR_GROUP_INVALID_POS: u8 = 1; 21 | pub const ERR_GROUP_LOCKED_NAME: u8 = 2; 22 | pub const ERR_LIMIT_REACHED: u8 = 3; 23 | pub const ERR_LOGIN_BANNED: u8 = 4; 24 | pub const ERR_LOGIN_BOT: u8 = 5; 25 | pub const ERR_LOGIN_INVALID: u8 = 6; 26 | pub const ERR_MAX_CONN_PER_IP: u8 = 7; 27 | pub const ERR_MISSING_FIELD: u8 = 8; 28 | pub const ERR_MISSING_PERMISSION: u8 = 9; 29 | pub const ERR_NAME_TAKEN: u8 = 10; 30 | pub const ERR_UNKNOWN_BOT: u8 = 11; 31 | pub const ERR_UNKNOWN_CHANNEL: u8 = 12; 32 | pub const ERR_UNKNOWN_GROUP: u8 = 13; 33 | pub const ERR_UNKNOWN_MESSAGE: u8 = 14; 34 | pub const ERR_UNKNOWN_USER: u8 = 15; 35 | 36 | pub const PERM_READ: u8 = 1; 37 | pub const PERM_WRITE: u8 = 1 << 1; 38 | 39 | pub const PERM_ASSIGN_GROUPS: u8 = 1 << 2; 40 | pub const PERM_BAN: u8 = 1 << 3; 41 | pub const PERM_MANAGE_CHANNELS: u8 = 1 << 4; 42 | pub const PERM_MANAGE_GROUPS: u8 = 1 << 5; 43 | pub const PERM_MANAGE_MESSAGES: u8 = 1 << 6; 44 | 45 | // TYPES 46 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 47 | pub struct Channel { 48 | pub id: usize, 49 | pub name: String, 50 | pub overrides: HashMap 51 | } 52 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 53 | pub struct Group { 54 | pub allow: u8, 55 | pub deny: u8, 56 | pub id: usize, 57 | pub name: String, 58 | pub pos: usize, 59 | pub unassignable: bool 60 | } 61 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 62 | pub struct Message { 63 | pub author: usize, 64 | pub channel: usize, 65 | pub id: usize, 66 | pub text: Vec, 67 | pub timestamp: i64, 68 | pub timestamp_edit: Option 69 | } 70 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 71 | pub struct User { 72 | pub ban: bool, 73 | pub bot: bool, 74 | pub groups: Vec, 75 | pub id: usize, 76 | pub name: String 77 | } 78 | 79 | // CLIENT PACKETS 80 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 81 | pub struct Close; 82 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 83 | pub struct ChannelCreate { 84 | pub name: String, 85 | pub overrides: HashMap 86 | } 87 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 88 | pub struct ChannelDelete { 89 | pub id: usize 90 | } 91 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 92 | pub struct ChannelUpdate { 93 | pub inner: Channel, 94 | pub keep_overrides: bool 95 | } 96 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 97 | pub struct Command { 98 | pub args: Vec, 99 | pub recipient: usize 100 | } 101 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 102 | pub struct GroupCreate { 103 | pub allow: u8, 104 | pub deny: u8, 105 | pub name: String, 106 | pub pos: usize, 107 | pub unassignable: bool 108 | } 109 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 110 | pub struct GroupDelete { 111 | pub id: usize 112 | } 113 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 114 | pub struct GroupUpdate { 115 | pub inner: Group 116 | } 117 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 118 | pub struct Login { 119 | pub bot: bool, 120 | pub name: String, 121 | pub password: Option, 122 | pub token: Option 123 | } 124 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 125 | pub struct LoginUpdate { 126 | pub name: Option, 127 | pub password_current: Option, 128 | pub password_new: Option, 129 | pub reset_token: bool 130 | } 131 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 132 | pub struct MessageCreate { 133 | pub channel: usize, 134 | pub text: Vec 135 | } 136 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 137 | pub struct MessageDelete { 138 | pub id: usize 139 | } 140 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 141 | pub struct MessageDeleteBulk { 142 | pub channel: usize, 143 | pub ids: Vec 144 | } 145 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 146 | pub struct MessageList { 147 | pub after: Option, 148 | pub before: Option, 149 | pub channel: usize, 150 | pub limit: usize 151 | } 152 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 153 | pub struct MessageUpdate { 154 | pub id: usize, 155 | pub text: Vec 156 | } 157 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 158 | pub struct PrivateMessage { 159 | pub text: Vec, 160 | pub recipient: usize 161 | } 162 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 163 | pub struct Typing { 164 | pub channel: usize 165 | } 166 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 167 | pub struct UserUpdate { 168 | pub ban: Option, 169 | pub groups: Option>, 170 | pub id: usize 171 | } 172 | 173 | // SERVER PACKETS 174 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 175 | pub struct ChannelDeleteReceive { 176 | pub inner: Channel 177 | } 178 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 179 | pub struct ChannelReceive { 180 | pub inner: Channel 181 | } 182 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 183 | pub struct CommandReceive { 184 | pub args: Vec, 185 | pub author: usize 186 | } 187 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 188 | pub struct GroupDeleteReceive { 189 | pub inner: Group 190 | } 191 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 192 | pub struct GroupReceive { 193 | pub inner: Group, 194 | pub new: bool 195 | } 196 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 197 | pub struct LoginSuccess { 198 | pub created: bool, 199 | pub id: usize, 200 | pub token: String 201 | } 202 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 203 | pub struct MessageDeleteReceive { 204 | pub id: usize 205 | } 206 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 207 | pub struct MessageReceive { 208 | pub inner: Message, 209 | pub new: bool 210 | } 211 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 212 | pub struct PMReceive { 213 | pub author: usize, 214 | pub text: Vec 215 | } 216 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 217 | pub struct TypingReceive { 218 | pub author: usize, 219 | pub channel: usize 220 | } 221 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 222 | pub struct UserReceive { 223 | pub inner: User 224 | } 225 | 226 | macro_rules! packet { 227 | ($($type:ident),+) => { 228 | #[derive(Clone, Debug, Deserialize, Serialize)] 229 | #[serde(rename_all = "snake_case")] 230 | pub enum Packet { 231 | Close, 232 | Err(u8), 233 | RateLimited(u64), 234 | $($type($type),)+ 235 | } 236 | } 237 | } 238 | packet! ( 239 | ChannelCreate, 240 | ChannelDelete, 241 | ChannelUpdate, 242 | Command, 243 | GroupCreate, 244 | GroupDelete, 245 | GroupUpdate, 246 | Login, 247 | LoginUpdate, 248 | MessageCreate, 249 | MessageDelete, 250 | MessageDeleteBulk, 251 | MessageList, 252 | MessageUpdate, 253 | PrivateMessage, 254 | Typing, 255 | UserUpdate, 256 | 257 | ChannelDeleteReceive, 258 | ChannelReceive, 259 | CommandReceive, 260 | GroupDeleteReceive, 261 | GroupReceive, 262 | LoginSuccess, 263 | MessageDeleteReceive, 264 | MessageReceive, 265 | PMReceive, 266 | TypingReceive, 267 | UserReceive 268 | ); 269 | 270 | pub fn serialize(packet: &Packet) -> Result, rmps::encode::Error> { 271 | rmps::to_vec(&packet) 272 | } 273 | pub fn deserialize(buf: &[u8]) -> Result { 274 | rmps::from_slice(buf) 275 | } 276 | pub fn deserialize_stream(buf: T) -> Result { 277 | rmps::from_read(buf) 278 | } 279 | pub fn encode_u16(input: u16) -> [u8; 2] { 280 | [ 281 | (input >> 8) as u8, 282 | (input % 256) as u8 283 | ] 284 | } 285 | pub fn decode_u16(bytes: &[u8]) -> u16 { 286 | assert_eq!(bytes.len(), 2); 287 | 288 | ((bytes[0] as u16) << 8) + bytes[1] as u16 289 | } 290 | 291 | #[derive(Debug, Fail)] 292 | pub enum Error { 293 | #[fail(display = "{}", _0)] 294 | DecodeError(#[cause] rmps::decode::Error), 295 | #[fail(display = "{}", _0)] 296 | EncodeError(#[cause] rmps::encode::Error), 297 | #[fail(display = "{}", _0)] 298 | IoError(#[cause] std::io::Error), 299 | 300 | #[fail(display = "Packet size must fit in an u16")] 301 | PacketTooBigError 302 | } 303 | 304 | impl From for Error { 305 | fn from(err: rmps::decode::Error) -> Self { 306 | Error::DecodeError(err) 307 | } 308 | } 309 | impl From for Error { 310 | fn from(err: rmps::encode::Error) -> Self { 311 | Error::EncodeError(err) 312 | } 313 | } 314 | impl From for Error { 315 | fn from(err: std::io::Error) -> Self { 316 | Error::IoError(err) 317 | } 318 | } 319 | 320 | pub fn read(reader: &mut T) -> Result { 321 | let mut buf = [0; 2]; 322 | reader.read_exact(&mut buf)?; 323 | 324 | let size = decode_u16(&buf) as usize; 325 | let mut buf = vec![0; size]; 326 | reader.read_exact(&mut buf)?; 327 | 328 | Ok(deserialize(&buf)?) 329 | } 330 | pub fn write(writer: &mut T, packet: &Packet) -> Result<(), Error> { 331 | let buf = serialize(packet)?; 332 | if buf.len() > std::u16::MAX as usize { 333 | return Err(Error::PacketTooBigError); 334 | } 335 | let size = encode_u16(buf.len() as u16); 336 | writer.write_all(&size)?; 337 | writer.write_all(&buf)?; 338 | writer.flush()?; 339 | 340 | Ok(()) 341 | } 342 | 343 | pub fn perm_apply_iter>(into: &mut u8, groups: &mut I) { 344 | // Expects groups to be sorted 345 | for group in groups { 346 | perm_apply(into, group); 347 | } 348 | } 349 | pub fn perm_apply(into: &mut u8, (allow, deny): (u8, u8)) { 350 | *into |= allow; 351 | *into &= !deny; 352 | } 353 | -------------------------------------------------------------------------------- /client/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "bitflags" 3 | version = "0.4.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "bitflags" 8 | version = "0.9.1" 9 | source = "registry+https://github.com/rust-lang/crates.io-index" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.0.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "byteorder" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | 21 | [[package]] 22 | name = "cc" 23 | version = "1.0.3" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | 26 | [[package]] 27 | name = "client" 28 | version = "0.3.6" 29 | dependencies = [ 30 | "openssl 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "rusqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "rustyline 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "synac 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 35 | ] 36 | 37 | [[package]] 38 | name = "encode_unicode" 39 | version = "0.1.3" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | 42 | [[package]] 43 | name = "foreign-types" 44 | version = "0.2.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | 47 | [[package]] 48 | name = "kernel32-sys" 49 | version = "0.2.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | dependencies = [ 52 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 54 | ] 55 | 56 | [[package]] 57 | name = "lazy_static" 58 | version = "0.2.11" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | 61 | [[package]] 62 | name = "libc" 63 | version = "0.2.33" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | 66 | [[package]] 67 | name = "libsqlite3-sys" 68 | version = "0.9.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | dependencies = [ 71 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 73 | ] 74 | 75 | [[package]] 76 | name = "linked-hash-map" 77 | version = "0.4.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | 80 | [[package]] 81 | name = "lru-cache" 82 | version = "0.1.1" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | dependencies = [ 85 | "linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "nix" 90 | version = "0.5.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | dependencies = [ 93 | "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 95 | ] 96 | 97 | [[package]] 98 | name = "num-traits" 99 | version = "0.1.40" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | 102 | [[package]] 103 | name = "openssl" 104 | version = "0.9.21" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | dependencies = [ 107 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 108 | "foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 109 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 110 | "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 111 | "openssl-sys 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)", 112 | ] 113 | 114 | [[package]] 115 | name = "openssl-sys" 116 | version = "0.9.21" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | dependencies = [ 119 | "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 122 | "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 123 | ] 124 | 125 | [[package]] 126 | name = "pkg-config" 127 | version = "0.3.9" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | 130 | [[package]] 131 | name = "quote" 132 | version = "0.3.15" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | 135 | [[package]] 136 | name = "redox_syscall" 137 | version = "0.1.31" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | 140 | [[package]] 141 | name = "redox_termios" 142 | version = "0.1.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | dependencies = [ 145 | "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", 146 | ] 147 | 148 | [[package]] 149 | name = "rmp" 150 | version = "0.8.7" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | dependencies = [ 153 | "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 155 | ] 156 | 157 | [[package]] 158 | name = "rmp-serde" 159 | version = "0.13.7" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | dependencies = [ 162 | "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "rmp 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "serde 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)", 165 | ] 166 | 167 | [[package]] 168 | name = "rusqlite" 169 | version = "0.13.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | dependencies = [ 172 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "libsqlite3-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 174 | "lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 175 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 176 | ] 177 | 178 | [[package]] 179 | name = "rustyline" 180 | version = "1.0.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | dependencies = [ 183 | "encode_unicode 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 184 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 185 | "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 186 | "nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 187 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 188 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 189 | ] 190 | 191 | [[package]] 192 | name = "serde" 193 | version = "1.0.21" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | 196 | [[package]] 197 | name = "serde_derive" 198 | version = "1.0.21" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | dependencies = [ 201 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "serde_derive_internals 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 204 | ] 205 | 206 | [[package]] 207 | name = "serde_derive_internals" 208 | version = "0.17.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | dependencies = [ 211 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 212 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 213 | ] 214 | 215 | [[package]] 216 | name = "syn" 217 | version = "0.11.11" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | dependencies = [ 220 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 221 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 222 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 223 | ] 224 | 225 | [[package]] 226 | name = "synac" 227 | version = "0.1.4" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | dependencies = [ 230 | "openssl 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)", 231 | "rmp-serde 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)", 232 | "serde 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)", 233 | "serde_derive 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)", 234 | ] 235 | 236 | [[package]] 237 | name = "synom" 238 | version = "0.11.3" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | dependencies = [ 241 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 242 | ] 243 | 244 | [[package]] 245 | name = "termion" 246 | version = "1.5.1" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | dependencies = [ 249 | "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 250 | "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", 251 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 252 | ] 253 | 254 | [[package]] 255 | name = "time" 256 | version = "0.1.38" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | dependencies = [ 259 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 260 | "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 261 | "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", 262 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 263 | ] 264 | 265 | [[package]] 266 | name = "unicode-width" 267 | version = "0.1.4" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | 270 | [[package]] 271 | name = "unicode-xid" 272 | version = "0.0.4" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | 275 | [[package]] 276 | name = "vcpkg" 277 | version = "0.2.2" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | 280 | [[package]] 281 | name = "winapi" 282 | version = "0.2.8" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | 285 | [[package]] 286 | name = "winapi-build" 287 | version = "0.1.1" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | 290 | [metadata] 291 | "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" 292 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 293 | "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" 294 | "checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" 295 | "checksum cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b13a57efd6b30ecd6598ebdb302cca617930b5470647570468a65d12ef9719" 296 | "checksum encode_unicode 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "28d65f1f5841ef7c6792861294b72beda34c664deb8be27970f36c306b7da1ce" 297 | "checksum foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e4056b9bd47f8ac5ba12be771f77a0dae796d1bbaaf5fd0b9c2d38b69b8a29d" 298 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 299 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 300 | "checksum libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "5ba3df4dcb460b9dfbd070d41c94c19209620c191b0340b929ce748a2bcd42d2" 301 | "checksum libsqlite3-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e9eb7b8e152b6a01be6a4a2917248381875758250dc3df5d46caf9250341dda" 302 | "checksum linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939" 303 | "checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21" 304 | "checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" 305 | "checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0" 306 | "checksum openssl 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)" = "2225c305d8f57001a0d34263e046794aa251695f20773102fbbfeb1e7b189955" 307 | "checksum openssl-sys 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)" = "92867746af30eea7a89feade385f7f5366776f1c52ec6f0de81360373fa88363" 308 | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 309 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 310 | "checksum redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "8dde11f18c108289bef24469638a04dce49da56084f2d50618b226e47eb04509" 311 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 312 | "checksum rmp 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a3d45d7afc9b132b34a2479648863aa95c5c88e98b32285326a6ebadc80ec5c9" 313 | "checksum rmp-serde 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)" = "011e1d58446e9fa3af7cdc1fb91295b10621d3ac4cb3a85cc86385ee9ca50cd3" 314 | "checksum rusqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9409d78a5a9646685688266e1833df8f08b71ffcae1b5db6c1bfb5970d8a80f" 315 | "checksum rustyline 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "00b06ac9c8e8e3e83b33d175d39a9f7b6c2c930c82990593719c8e48788ae2d9" 316 | "checksum serde 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6eda663e865517ee783b0891a3f6eb3a253e0b0dabb46418969ee9635beadd9e" 317 | "checksum serde_derive 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)" = "652bc323d694dc925829725ec6c890156d8e70ae5202919869cb00fe2eff3788" 318 | "checksum serde_derive_internals 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "32f1926285523b2db55df263d2aa4eb69ddcfa7a7eade6430323637866b513ab" 319 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 320 | "checksum synac 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "52dd85144cfb3e6002f50b37c9db7e9afd5ec3ac0a30df9b34b193539c9e9ea7" 321 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 322 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 323 | "checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" 324 | "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" 325 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 326 | "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" 327 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 328 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 329 | -------------------------------------------------------------------------------- /client/src/frontend.rs: -------------------------------------------------------------------------------- 1 | extern crate termion; 2 | 3 | use *; 4 | use rustyline::completion::{extract_word, Completer as RustyCompleter}; 5 | use rustyline::error::ReadlineError; 6 | use rustyline; 7 | use self::termion::screen::AlternateScreen; 8 | use self::termion::{cursor, color}; 9 | use std::cmp; 10 | use std::io::{self, Write}; 11 | use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; 12 | use std::sync::{Mutex, RwLock}; 13 | 14 | pub struct MuteGuard<'a>(&'a Screen); 15 | impl<'a> Drop for MuteGuard<'a> { 16 | fn drop(&mut self) { 17 | self.0.mute.store(false, AtomicOrdering::Relaxed); 18 | self.0.repaint(); 19 | } 20 | } 21 | 22 | pub fn sanitize(text: String) -> String { 23 | // text.retain(|c| { 24 | // !c.is_control() || c == '\n' || c == '\t' 25 | // }); 26 | // YES, that's right. Until that's stabilized, I'd rather clone the entire string 27 | // than using difficult workarounds. 28 | // TODO: Use `retain` to avoid allocation. 29 | text.chars().filter(|c| !c.is_control() || *c == '\n' || *c == '\t').collect() 30 | } 31 | 32 | struct Completer { 33 | session: Arc>> 34 | } 35 | 36 | impl RustyCompleter for Completer { 37 | fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec), ReadlineError> { 38 | let break_chars = &[' ', '"', '\''].into_iter().cloned().collect(); 39 | let (start, word) = extract_word(line, pos, &break_chars); 40 | 41 | let mut output = Vec::new(); 42 | 43 | if let Some(ref session) = *self.session.lock().unwrap() { 44 | output.extend(session.state.users.values() 45 | .map(|user| user.name.clone()) 46 | .filter(|name| name.starts_with(word))); 47 | output.extend(session.state.channels.values() 48 | .map(|channel| { 49 | let mut name = String::with_capacity(channel.name.len() + 1); 50 | name.push('#'); 51 | name.push_str(&channel.name); 52 | name 53 | }) 54 | .filter(|name| name.starts_with(word))); 55 | } 56 | 57 | Ok((start, output)) 58 | } 59 | } 60 | 61 | pub struct Screen { 62 | editor: Mutex>, 63 | log: RwLock>, 64 | mute: AtomicBool, 65 | status: Mutex>, 66 | stdin: Mutex, 67 | stdout: Mutex>, 68 | typing: RwLock 69 | } 70 | impl Screen { 71 | pub fn new(session: &Arc>>) -> Screen { 72 | let stdin = io::stdin(); 73 | stdin.lock(); 74 | let stdout = io::stdout(); 75 | stdout.lock(); 76 | 77 | let mut editor = rustyline::Editor::::new(); 78 | editor.set_completer(Some(Completer { 79 | session: Arc::clone(session), 80 | })); 81 | 82 | Screen { 83 | editor: Mutex::new(editor), 84 | log: RwLock::new(Vec::new()), 85 | mute: AtomicBool::new(false), 86 | status: Mutex::new(Vec::new()), 87 | stdin: Mutex::new(stdin), 88 | stdout: Mutex::new(AlternateScreen::from(stdout)), 89 | typing: RwLock::new(String::new()) 90 | } 91 | } 92 | 93 | pub fn clear(&self) { 94 | self.log.write().unwrap().clear(); 95 | self.repaint(); 96 | } 97 | 98 | pub fn log(&self, text: String) { 99 | self.log_with_id(text, LogEntryId::None); 100 | } 101 | pub fn log_with_id(&self, mut text: String, id: LogEntryId) { 102 | if let LogEntryId::Message(_) = id { 103 | if let Some(text2) = self.edit(text, id) { 104 | text = text2; 105 | } else { 106 | self.repaint(); 107 | return; 108 | } 109 | } 110 | self.log.write().unwrap().push((text, id)); 111 | self.repaint(); 112 | } 113 | pub fn edit(&self, text: String, id: LogEntryId) -> Option { 114 | let mut log = self.log.write().unwrap(); 115 | for &mut (ref mut entry_text, entry_id) in &mut *log { 116 | if entry_id == id { 117 | *entry_text = text; 118 | return None; 119 | } 120 | } 121 | Some(text) 122 | } 123 | pub fn delete(&self, id: LogEntryId) { 124 | let mut log = self.log.write().unwrap(); 125 | let pos = log.iter().position(|&(_, entry_id)| entry_id == id); 126 | if let Some(pos) = pos { 127 | log.remove(pos); 128 | } 129 | // TODO: remove_item once stable 130 | } 131 | 132 | pub fn update(&self, session: &Session) { 133 | let mut status = self.status.lock().unwrap(); 134 | status.clear(); 135 | 136 | status.push(String::new()); 137 | status.push(String::from("Channels:")); 138 | for channel in session.state.channels.values() { 139 | let mut string = String::with_capacity(2 + channel.name.len()); 140 | string.push_str(" #"); 141 | string.push_str(&channel.name); 142 | status.push(string); 143 | } 144 | status[2..].sort_unstable(); 145 | } 146 | pub fn repaint(&self) { 147 | if self.mute.load(AtomicOrdering::Relaxed) { 148 | return; 149 | } 150 | self.repaint_(&**self.log.read().unwrap()); 151 | } 152 | fn repaint_(&self, mut log: &[(String, LogEntryId)]) { 153 | let mut stdout = self.stdout.lock().unwrap(); 154 | let (width, height) = termion::terminal_size().unwrap_or((50, 50)); 155 | let cw = width.saturating_sub(11 + 2); 156 | let ch = height.saturating_sub(3); 157 | 158 | let status = self.status.lock().unwrap(); 159 | 160 | let mut index = 0; 161 | let mut status = |stdout: &mut AlternateScreen, left| { 162 | if let Some(text) = status.get(index) { 163 | let mut text = &text as &str; 164 | let mut ellipsis = ""; 165 | if text.len() > 11 { 166 | text = &text[..10]; 167 | ellipsis = "…"; 168 | } 169 | index += 1; 170 | writeln!(stdout, "{}{}{}", cursor::Right(cw + 2 - left), text, ellipsis).unwrap(); 171 | } else { 172 | writeln!(stdout).unwrap(); 173 | } 174 | }; 175 | 176 | loop { 177 | let mut lines = 0; 178 | for msg in log { 179 | lines += 1; 180 | 181 | let indent_amount = msg.0.find(": ").map(|i| i+2).unwrap_or_default(); 182 | 183 | let mut i = 0; 184 | for c in msg.0.chars() { 185 | if (i != 0 && i == cw as usize) || c == '\n' { 186 | i = indent_amount; 187 | lines += 1; 188 | } else { 189 | i += 1; 190 | } 191 | } 192 | } 193 | 194 | if lines <= ch { 195 | break; 196 | } 197 | log = &log[1..]; 198 | } 199 | 200 | write!(stdout, "{}{}", termion::clear::All, cursor::Goto(1, 1)).unwrap(); 201 | 202 | for &(ref text, id) in log { 203 | let indent_amount = text.find(": ").map(|i| i+2).unwrap_or_default(); 204 | 205 | let mut first = true; 206 | let mut indent = String::new(); 207 | let mut skip = 0; 208 | let mut text = &**text; 209 | 210 | if text.is_empty() { 211 | status(&mut stdout, 0); 212 | } 213 | while !text.is_empty() { 214 | if !first && indent.is_empty() { 215 | indent = " ".repeat(indent_amount); 216 | } 217 | let indent_amount = if first { 0 } else { indent_amount }; 218 | first = false; 219 | 220 | text = &text[skip..]; 221 | skip = 0; 222 | 223 | let newline = text.find('\n').unwrap_or(std::usize::MAX); 224 | let width = cmp::min(newline, cmp::min((cw as usize).saturating_sub(indent_amount), text.len())); 225 | if let LogEntryId::Sending = id { 226 | write!( 227 | stdout, 228 | "{}{}{}{}", 229 | indent, 230 | color::Fg(color::LightBlack), 231 | &text[..width], 232 | color::Fg(color::Reset) 233 | ).unwrap(); 234 | } else { 235 | write!(stdout, "{}{}", indent, &text[..width]).unwrap(); 236 | } 237 | status(&mut stdout, (indent_amount + width) as u16); 238 | text = &text[width..]; 239 | if width == newline { 240 | skip += 1; 241 | } 242 | } 243 | } 244 | 245 | write!(stdout, "{}{}", cursor::Goto(1, height), self.typing.read().unwrap()).unwrap(); 246 | write!(stdout, "{}> ", cursor::Goto(1, height-1)).unwrap(); 247 | stdout.flush().unwrap(); 248 | } 249 | pub fn mute(&self) -> MuteGuard { 250 | self.mute.store(true, AtomicOrdering::Relaxed); 251 | MuteGuard(self) 252 | } 253 | 254 | pub fn readline(&self) -> Result { 255 | let mut error = None; 256 | let ret = { 257 | let mut editor = self.editor.lock().unwrap(); 258 | match editor.readline("> ") { 259 | Ok(some) => { editor.add_history_entry(&some); Ok(some) }, 260 | Err(ReadlineError::Interrupted) | 261 | Err(ReadlineError::Eof) => Err(()), 262 | Err(err) => { 263 | error = Some(err); 264 | Err(()) 265 | } 266 | } 267 | }; 268 | if let Some(err) = error { 269 | self.log(format!("Failed to read line: {}", err)); 270 | } 271 | ret 272 | } 273 | pub fn readpass(&self) -> Result { 274 | use self::termion::input::TermRead; 275 | let mut error = None; 276 | let ret = { 277 | match self.stdin.lock().unwrap().read_passwd(&mut *self.stdout.lock().unwrap()) { 278 | Ok(Some(some)) => Ok(some), 279 | Ok(None) => Err(()), 280 | Err(err) => { 281 | error = Some(err); 282 | Err(()) 283 | } 284 | } 285 | }; 286 | if let Some(err) = error { 287 | self.log(format!("Failed to read password: {}", err)); 288 | } 289 | ret 290 | } 291 | 292 | pub fn typing_set(&self, typing: String) { 293 | if *self.typing.read().unwrap() == typing { 294 | return; 295 | } 296 | *self.typing.write().unwrap() = typing; 297 | self.repaint(); 298 | } 299 | 300 | pub fn get_channel_overrides(&self, mut overrides: HashMap, session: &Session) 301 | -> Result, ()> { 302 | let _guard = self.mute(); 303 | let log = Vec::with_capacity(2); 304 | 305 | macro_rules! println { 306 | () => { self.log(String::new()); }; 307 | ($arg:expr) => { self.log(String::from($arg)); }; 308 | ($($arg:expr),*) => { self.log(format!($($arg),*)); }; 309 | } 310 | 311 | loop { 312 | let result = overrides.iter().fold(String::new(), |mut acc, (role, &(allow, deny))| { 313 | if !acc.is_empty() { acc.push_str(", "); } 314 | acc.push_str(&role.to_string()); 315 | acc.push_str(": "); 316 | acc.push_str(&to_perm_string(allow, deny)); 317 | acc 318 | }); 319 | println!("Overrides: [{}]", result); 320 | println!("Commands: set, unset, quit"); 321 | self.repaint_(&log); 322 | 323 | let line = self.readline()?; 324 | 325 | let parts = parser::parse(&line); 326 | if parts.is_empty() { continue; } 327 | let set = match &*parts[0] { 328 | "set" => true, 329 | "unset" => false, 330 | "quit" => break, 331 | _ => { 332 | println!("Unknown command"); 333 | continue; 334 | } 335 | }; 336 | 337 | if set && parts.len() != 3 { 338 | println!("Usage: set [perms]"); 339 | continue; 340 | } else if !set && parts.len() != 2 { 341 | println!("Usage: unset "); 342 | continue; 343 | } 344 | let id = match parts[1].parse() { 345 | Ok(ok) => ok, 346 | Err(_) => { 347 | println!("Invalid ID"); 348 | continue; 349 | } 350 | }; 351 | if let Some(group) = session.state.groups.get(&id) { 352 | if set { 353 | let (mut allow, mut deny) = (0, 0); 354 | if let Some(perms) = overrides.get(&id) { 355 | allow = perms.0; 356 | deny = perms.1; 357 | } 358 | if !from_perm_string(&parts[2], &mut allow, &mut deny) { 359 | println!("Invalid permission string"); 360 | continue; 361 | } 362 | overrides.insert(id, (allow, deny)); 363 | println!("Set \"{}\" to {}", group.name, to_perm_string(allow, deny)); 364 | } else { 365 | overrides.remove(&id); 366 | println!("Unset: {}", group.name); 367 | } 368 | } else { 369 | println!("Could not find group"); 370 | } 371 | } 372 | Ok(overrides) 373 | } 374 | pub fn get_user_groups(&self, mut groups: Vec, session: &Session) -> Result, ()> { 375 | let _guard = self.mute(); 376 | let log = Vec::with_capacity(2); 377 | 378 | macro_rules! println { 379 | () => { self.log(String::new()); }; 380 | ($arg:expr) => { self.log(String::from($arg)); }; 381 | ($($arg:expr),*) => { self.log(format!($($arg),*)); }; 382 | } 383 | 384 | loop { 385 | let result = groups.iter().fold(String::new(), |mut acc, item| { 386 | if !acc.is_empty() { acc.push_str(", "); } 387 | acc.push_str(&item.to_string()); 388 | acc 389 | }); 390 | println!("Groups: [{}]", result); 391 | println!("Commands: add, remove, quit"); 392 | self.repaint_(&log); 393 | 394 | let line = self.readline()?; 395 | 396 | let parts = parser::parse(&line); 397 | if parts.is_empty() { continue; } 398 | let add = match &*parts[0] { 399 | "add" => true, 400 | "remove" => false, 401 | "quit" => break, 402 | _ => { 403 | println!("Unknown command"); 404 | continue; 405 | } 406 | }; 407 | 408 | if parts.len() != 2 { 409 | println!("Usage: add/remove "); 410 | continue; 411 | } 412 | let id = match parts[1].parse() { 413 | Ok(ok) => ok, 414 | Err(_) => { 415 | println!("Invalid ID"); 416 | continue; 417 | } 418 | }; 419 | if let Some(group) = session.state.groups.get(&id) { 420 | if group.pos == 0 { 421 | println!("Unable to assign that group"); 422 | continue; 423 | } 424 | if add { 425 | if !groups.contains(&id) { 426 | groups.push(id); 427 | groups.sort_unstable(); 428 | } 429 | println!("Added: {}", group.name); 430 | } else { 431 | let pos = groups.iter().position(|item| *item == id); 432 | if let Some(pos) = pos { 433 | groups.remove(pos); 434 | } 435 | // TODO remove_item once stable 436 | println!("Removed: {}", group.name); 437 | } 438 | } else { 439 | println!("Could not find group"); 440 | } 441 | } 442 | Ok(groups) 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /client-gtk/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate failure; 2 | extern crate chrono; 3 | extern crate gtk; 4 | extern crate rusqlite; 5 | extern crate synac; 6 | extern crate xdg; 7 | 8 | mod connections; 9 | mod messages; 10 | 11 | use gtk::prelude::*; 12 | use gtk::{ 13 | Align, 14 | Box as GtkBox, 15 | Button, 16 | ButtonsType, 17 | Dialog, 18 | DialogFlags, 19 | Entry, 20 | InputPurpose, 21 | Label, 22 | Menu, 23 | MenuItem, 24 | MessageDialog, 25 | MessageType, 26 | Orientation, 27 | ResponseType, 28 | ScrolledWindow, 29 | Separator, 30 | Stack, 31 | Window, 32 | WindowType 33 | }; 34 | use connections::{Connections, Synac}; 35 | use rusqlite::Connection as SqlConnection; 36 | use std::env; 37 | use std::net::SocketAddr; 38 | use std::rc::Rc; 39 | use std::sync::Arc; 40 | use synac::common::{self, Packet}; 41 | use xdg::BaseDirectories; 42 | 43 | fn main() { 44 | let basedirs = match BaseDirectories::with_prefix("synac") { 45 | Ok(basedirs) => basedirs, 46 | Err(err) => { eprintln!("error initializing xdg: {}", err); return; } 47 | }; 48 | let path = match basedirs.find_data_file("data.sqlite") { 49 | Some(path) => path, 50 | None => match basedirs.place_data_file("data.sqlite") { 51 | Ok(path) => path, 52 | Err(err) => { eprintln!("error placing config: {}", err); return; } 53 | } 54 | }; 55 | let db = match SqlConnection::open(&path) { 56 | Ok(ok) => ok, 57 | Err(err) => { 58 | eprintln!("Failed to open database"); 59 | eprintln!("{}", err); 60 | return; 61 | } 62 | }; 63 | let db = Rc::new(db); 64 | db.execute("CREATE TABLE IF NOT EXISTS data ( 65 | key TEXT NOT NULL UNIQUE, 66 | value TEXT NOT NULL 67 | )", &[]) 68 | .expect("Couldn't create SQLite table"); 69 | db.execute("CREATE TABLE IF NOT EXISTS servers ( 70 | ip TEXT NOT NULL PRIMARY KEY, 71 | name TEXT NOT NULL, 72 | hash BLOB NOT NULL, 73 | token TEXT 74 | )", &[]) 75 | .expect("Couldn't create SQLite table"); 76 | 77 | let nick = { 78 | let mut stmt = db.prepare("SELECT value FROM data WHERE key = 'nick'").unwrap(); 79 | let mut rows = stmt.query(&[]).unwrap(); 80 | 81 | if let Some(row) = rows.next() { 82 | row.unwrap().get::<_, String>(0) 83 | } else { 84 | #[cfg(unix)] 85 | { env::var("USER").unwrap_or_else(|_| String::from("unknown")) } 86 | #[cfg(windows)] 87 | { env::var("USERNAME").unwrap_or_else(|_| String::from("unknown")) } 88 | #[cfg(not(any(unix, windows)))] 89 | { String::from("unknown") } 90 | } 91 | }; 92 | 93 | if let Err(err) = gtk::init() { 94 | eprintln!("gtk error: {}", err); 95 | return; 96 | } 97 | 98 | let connections = Connections::new(&db, nick); 99 | 100 | let window = Window::new(WindowType::Toplevel); 101 | window.set_title("Synac GTK+ client"); 102 | window.set_default_size(1000, 700); 103 | 104 | let stack = Stack::new(); 105 | let main = GtkBox::new(Orientation::Horizontal, 10); 106 | 107 | let servers_wrapper = GtkBox::new(Orientation::Vertical, 0); 108 | 109 | let user_name = Label::new(&**connections.nick.read().unwrap()); 110 | user_name.set_property_margin(10); 111 | servers_wrapper.add(&user_name); 112 | 113 | servers_wrapper.add(&Separator::new(Orientation::Vertical)); 114 | 115 | // Are used in the future, but needed right now 116 | let channels = GtkBox::new(Orientation::Vertical, 2); 117 | let server_name = Label::new(""); 118 | let messages = GtkBox::new(Orientation::Vertical, 2); 119 | // end 120 | 121 | let servers = GtkBox::new(Orientation::Vertical, 2); 122 | render_servers(&connections, &db, &servers, &server_name, &channels, &messages, &window); 123 | servers_wrapper.add(&servers); 124 | 125 | let add = Button::new_with_label("Add..."); 126 | add.set_valign(Align::End); 127 | add.set_vexpand(true); 128 | 129 | servers_wrapper.add(&add); 130 | 131 | main.add(&servers_wrapper); 132 | 133 | main.add(&Separator::new(Orientation::Horizontal)); 134 | 135 | let channels_wrapper = GtkBox::new(Orientation::Vertical, 0); 136 | 137 | server_name.set_property_margin(10); 138 | channels_wrapper.add(&server_name); 139 | 140 | channels_wrapper.add(&Separator::new(Orientation::Vertical)); 141 | 142 | channels_wrapper.add(&channels); 143 | 144 | main.add(&channels_wrapper); 145 | 146 | main.add(&Separator::new(Orientation::Horizontal)); 147 | 148 | let content = GtkBox::new(Orientation::Vertical, 2); 149 | 150 | let channel_name = Label::new("Channel 1"); 151 | channel_name.set_property_margin(10); 152 | content.add(&channel_name); 153 | 154 | content.add(&Separator::new(Orientation::Vertical)); 155 | 156 | messages.set_vexpand(true); 157 | let scroll = ScrolledWindow::new(None, None); 158 | scroll.add(&messages); 159 | content.add(&scroll); 160 | 161 | let input = Entry::new(); 162 | input.set_hexpand(true); 163 | messages.set_valign(Align::Center); 164 | input.set_placeholder_text("Send a message"); 165 | content.add(&input); 166 | 167 | main.add(&content); 168 | stack.add(&main); 169 | 170 | let add_server = GtkBox::new(Orientation::Vertical, 2); 171 | 172 | let name = Entry::new(); 173 | name.set_placeholder_text("Name"); 174 | add_server.add(&name); 175 | add_server.add(&Label::new("The server name. This can be anything you want it to.")); 176 | 177 | let server = Entry::new(); 178 | server.set_placeholder_text("Server IP"); 179 | add_server.add(&server); 180 | add_server.add(&Label::new(&*format!("The server IP address. The default port is {}.", common::DEFAULT_PORT))); 181 | 182 | let hash = Entry::new(); 183 | hash.set_placeholder_text("Server's certificate hash"); 184 | add_server.add(&hash); 185 | add_server.add(&Label::new("The server's certificate public key hash.\n\ 186 | This is to verify nobody is snooping on your connection")); 187 | 188 | let add_server_controls = GtkBox::new(Orientation::Horizontal, 2); 189 | 190 | let add_server_cancel = Button::new_with_label("Cancel"); 191 | let stack_clone = stack.clone(); 192 | let main_clone = main.clone(); 193 | add_server_cancel.connect_clicked(move |_| { 194 | stack_clone.set_visible_child(&main_clone); 195 | }); 196 | add_server_controls.add(&add_server_cancel); 197 | 198 | let add_server_ok = Button::new_with_label("Ok"); 199 | 200 | let db_clone = Rc::clone(&db); 201 | let channels_clone = channels.clone(); 202 | let connections_clone = Arc::clone(&connections); 203 | let main_clone = main.clone(); 204 | let messages_clone = messages.clone(); 205 | let server_name_clone = server_name.clone(); 206 | let servers_clone = servers.clone(); 207 | let stack_clone = stack.clone(); 208 | let window_clone = window.clone(); 209 | add_server_ok.connect_clicked(move |_| { 210 | let name_text = name.get_text().unwrap_or_default(); 211 | let server_text = server.get_text().unwrap_or_default(); 212 | let hash_text = hash.get_text().unwrap_or_default(); 213 | 214 | let addr = match connections::parse_addr(&server_text) { 215 | Some(addr) => addr, 216 | None => return 217 | }; 218 | 219 | name.set_text(""); 220 | server.set_text(""); 221 | hash.set_text(""); 222 | 223 | stack_clone.set_visible_child(&main_clone); 224 | 225 | db_clone.execute( 226 | "INSERT INTO servers (name, ip, hash) VALUES (?, ?, ?)", 227 | &[&name_text, &addr.to_string(), &hash_text] 228 | ).unwrap(); 229 | render_servers(&connections_clone, &db_clone, &servers_clone, 230 | &server_name_clone, &channels_clone, &messages_clone, 231 | &window_clone); 232 | }); 233 | add_server_controls.add(&add_server_ok); 234 | 235 | add_server.add(&add_server_controls); 236 | stack.add(&add_server); 237 | 238 | let stack_clone = stack.clone(); 239 | let add_server = add_server.clone(); 240 | add.connect_clicked(move |_| { 241 | stack_clone.set_visible_child(&add_server); 242 | }); 243 | 244 | window.add(&stack); 245 | window.show_all(); 246 | window.connect_delete_event(|_, _| { 247 | gtk::main_quit(); 248 | Inhibit(false) 249 | }); 250 | 251 | let channels_clone = channels.clone(); 252 | let messages_clone = messages.clone(); 253 | gtk::idle_add(move || { 254 | let mut channels = false; 255 | let mut messages = false; 256 | let mut addr = None; 257 | 258 | if let Err(err) = connections.try_read(|synac, packet| { 259 | println!("received {:?}", packet); 260 | if *connections.current_server.lock().unwrap() != Some(synac.addr) { 261 | return; 262 | } 263 | addr = Some(synac.addr); 264 | match packet { 265 | Packet::ChannelReceive(_) => channels = true, 266 | Packet::ChannelDeleteReceive(_) => channels = true, 267 | Packet::MessageReceive(_) => messages = true, 268 | Packet::MessageDeleteReceive(_) => messages = true, 269 | _ => {} 270 | } 271 | }) { 272 | eprintln!("receive error: {}", err); 273 | return Continue(true); 274 | } 275 | 276 | if let Some(addr) = addr { 277 | if channels { 278 | render_channels(Some((&connections, addr)), &channels_clone, &messages_clone); 279 | } else if messages { 280 | render_messages(Some((&connections, addr)), &messages_clone); 281 | } 282 | } 283 | Continue(true) 284 | }); 285 | gtk::main(); 286 | } 287 | fn alert(window: &Window, kind: MessageType, message: &str) { 288 | let dialog = MessageDialog::new( 289 | Some(window), 290 | DialogFlags::MODAL, 291 | kind, 292 | ButtonsType::Ok, 293 | message 294 | ); 295 | dialog.connect_response(|dialog, _| dialog.destroy()); 296 | dialog.show_all(); 297 | } 298 | fn render_servers(connections: &Arc, db: &Rc, servers: &GtkBox, 299 | server_name: &Label, channels: &GtkBox, messages: &GtkBox, window: &Window) { 300 | for child in servers.get_children() { 301 | servers.remove(&child); 302 | } 303 | let mut stmt = db.prepare("SELECT ip, name, hash, token FROM servers ORDER BY name").unwrap(); 304 | let mut rows = stmt.query(&[]).unwrap(); 305 | 306 | while let Some(row) = rows.next() { 307 | let row = row.unwrap(); 308 | let addr: String = row.get(0); 309 | let name: String = row.get(1); 310 | let hash: String = row.get(2); 311 | let token: Option = row.get(3); 312 | 313 | let ip_parsed = connections::parse_addr(&addr); 314 | 315 | let button = Button::new_with_label(&name); 316 | let channels_clone = channels.clone(); 317 | let connections_clone = Arc::clone(connections); 318 | let db_clone = Rc::clone(db); 319 | let messages_clone = messages.clone(); 320 | let server_name_clone = server_name.clone(); 321 | let window_clone = window.clone(); 322 | button.connect_clicked(move |_| { 323 | let addr = match ip_parsed { 324 | Some(addr) => addr, 325 | None => { 326 | alert(&window_clone, MessageType::Error, "Failed to parse IP address. Format: "); 327 | return; 328 | } 329 | }; 330 | println!("Server with IP {} was clicked", addr); 331 | server_name_clone.set_text(&name); 332 | let mut ok = false; 333 | connections_clone.execute(addr, |result| { 334 | ok = result.is_ok(); 335 | }); 336 | if ok { 337 | connections_clone.set_current(Some(addr)); 338 | render_channels(Some((&connections_clone, addr)), &channels_clone, &messages_clone); 339 | } else { 340 | let result = connections_clone.connect(addr, hash.clone(), token.clone(), || { 341 | let dialog = Dialog::new_with_buttons( 342 | Some("Synac: Password dialog"), 343 | Some(&window_clone), 344 | DialogFlags::MODAL, 345 | &[("Ok", ResponseType::Ok.into())] 346 | ); 347 | 348 | let content = dialog.get_content_area(); 349 | content.add(&Label::new("Password:")); 350 | let entry = Entry::new(); 351 | entry.set_input_purpose(InputPurpose::Password); 352 | entry.set_visibility(false); 353 | content.add(&entry); 354 | 355 | dialog.show_all(); 356 | dialog.run(); 357 | let text = entry.get_text().unwrap_or_default(); 358 | dialog.destroy(); 359 | Some((text, Rc::clone(&db_clone))) 360 | }); 361 | match result { 362 | Ok(session) => { 363 | let synac = Synac::new(addr, session); 364 | connections_clone.insert(addr, synac); 365 | connections_clone.set_current(Some(addr)); 366 | render_channels(Some((&connections_clone, addr)), &channels_clone, &messages_clone); 367 | }, 368 | Err(err) => { 369 | alert(&window_clone, MessageType::Error, &format!("connection error: {}", err)); 370 | server_name_clone.set_text(""); 371 | connections_clone.set_current(None); 372 | render_channels(None, &channels_clone, &messages_clone); 373 | } 374 | } 375 | } 376 | }); 377 | 378 | let channels_clone = channels.clone(); 379 | let connections_clone = Arc::clone(connections); 380 | let db_clone = Rc::clone(db); 381 | let messages_clone = messages.clone(); 382 | let server_name_clone = server_name.clone(); 383 | let servers_clone = servers.clone(); 384 | let window_clone = window.clone(); 385 | button.connect_button_press_event(move |_, event| { 386 | if event.get_button() == 3 { 387 | let menu = Menu::new(); 388 | 389 | let forget = MenuItem::new_with_label("Forget server"); 390 | 391 | let channels_clone = channels_clone.clone(); 392 | let connections_clone = Arc::clone(&connections_clone); 393 | let db_clone = Rc::clone(&db_clone); 394 | let ip_clone = addr.clone(); 395 | let messages_clone = messages_clone.clone(); 396 | let server_name_clone = server_name_clone.clone(); 397 | let servers_clone = servers_clone.clone(); 398 | let window_clone = window_clone.clone(); 399 | forget.connect_activate(move |_| { 400 | db_clone.execute("DELETE FROM servers WHERE ip = ?", &[&ip_clone.to_string()]).unwrap(); 401 | render_servers(&connections_clone, &db_clone, &servers_clone, 402 | &server_name_clone, &channels_clone, &messages_clone, 403 | &window_clone); 404 | }); 405 | menu.add(&forget); 406 | 407 | menu.show_all(); 408 | menu.popup_at_pointer(Some(&**event)); 409 | } 410 | Inhibit(false) 411 | }); 412 | servers.add(&button); 413 | } 414 | servers.show_all(); 415 | servers.queue_draw(); 416 | } 417 | fn render_channels(connection: Option<(&Arc, SocketAddr)>, channels: &GtkBox, messages: &GtkBox) { 418 | for child in channels.get_children() { 419 | channels.remove(&child); 420 | } 421 | if let Some((connection, addr)) = connection { 422 | connection.execute(addr, |result| { 423 | if let Ok(server) = result { 424 | let mut channel_list: Vec<_> = server.state.channels.values().collect(); 425 | channel_list.sort_by_key(|channel| &channel.name); 426 | for channel in channel_list { 427 | let button = Button::new_with_label(&channel.name); 428 | let channel_id = channel.id; 429 | let connection_clone = connection.clone(); 430 | let messages_clone = messages.clone(); 431 | button.connect_clicked(move |_| { 432 | connection_clone.execute(addr, |result| { 433 | if let Ok(server) = result { 434 | server.channel = Some(channel_id); 435 | if !server.messages.has(channel_id) { 436 | if let Err(err) = server.session.send(&Packet::MessageList(common::MessageList { 437 | after: None, 438 | before: None, 439 | channel: channel_id, 440 | limit: common::LIMIT_BULK 441 | })) { 442 | eprintln!("error sending packet: {}", err); 443 | } 444 | } 445 | } 446 | }); 447 | render_messages(Some((&connection_clone, addr)), &messages_clone); 448 | }); 449 | channels.add(&button); 450 | } 451 | } 452 | }); 453 | } 454 | 455 | channels.show_all(); 456 | channels.queue_draw(); 457 | } 458 | fn render_messages(connection: Option<(&Arc, SocketAddr)>, messages: &GtkBox) { 459 | for child in messages.get_children() { 460 | messages.remove(&child); 461 | } 462 | if let Some(connection) = connection { 463 | connection.0.execute(connection.1, |result| { 464 | if let Ok(server) = result { 465 | if let Some(channel) = server.channel { 466 | for msg in server.messages.get(channel) { 467 | let msgbox = GtkBox::new(Orientation::Vertical, 2); 468 | let authorbox = GtkBox::new(Orientation::Horizontal, 4); 469 | 470 | let author = Label::new(&*server.state.users[&msg.author].name); 471 | author.set_xalign(0.0); 472 | authorbox.add(&author); 473 | 474 | authorbox.add(&Separator::new(Orientation::Horizontal)); 475 | 476 | let mut time = String::with_capacity(32); // just a guess 477 | messages::format(&mut time, msg.timestamp); 478 | if let Some(edit) = msg.timestamp_edit { 479 | time.push_str(" (edited "); 480 | messages::format(&mut time, edit); 481 | time.push_str(")"); 482 | } 483 | let time = Label::new(&*time); 484 | time.set_xalign(0.0); 485 | authorbox.add(&time); 486 | 487 | msgbox.add(&authorbox); 488 | 489 | let text = Label::new(&*String::from_utf8_lossy(&msg.text)); 490 | text.set_xalign(0.0); 491 | msgbox.add(&text); 492 | 493 | messages.add(&msgbox); 494 | 495 | messages.add(&Separator::new(Orientation::Vertical)); 496 | } 497 | } 498 | } 499 | }); 500 | } 501 | messages.show_all(); 502 | messages.queue_draw(); 503 | } 504 | -------------------------------------------------------------------------------- /server/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "backtrace" 3 | version = "0.3.4" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 11 | "rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 13 | ] 14 | 15 | [[package]] 16 | name = "backtrace-sys" 17 | version = "0.1.16" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | dependencies = [ 20 | "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 21 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "bcrypt" 26 | version = "0.1.3" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | dependencies = [ 29 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 33 | ] 34 | 35 | [[package]] 36 | name = "bitflags" 37 | version = "0.7.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "0.9.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | 45 | [[package]] 46 | name = "byteorder" 47 | version = "1.1.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | 50 | [[package]] 51 | name = "bytes" 52 | version = "0.4.5" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | dependencies = [ 55 | "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 57 | ] 58 | 59 | [[package]] 60 | name = "cc" 61 | version = "1.0.3" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | 64 | [[package]] 65 | name = "cfg-if" 66 | version = "0.1.2" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | 69 | [[package]] 70 | name = "chrono" 71 | version = "0.4.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 76 | ] 77 | 78 | [[package]] 79 | name = "common" 80 | version = "0.1.0" 81 | dependencies = [ 82 | "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "rmp-serde 0.13.6 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "serde_derive 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "conv" 90 | version = "0.3.3" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | dependencies = [ 93 | "custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "custom_derive" 98 | version = "0.1.7" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | 101 | [[package]] 102 | name = "dbghelp-sys" 103 | version = "0.2.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | dependencies = [ 106 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "dtoa" 112 | version = "0.4.2" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | 115 | [[package]] 116 | name = "failure" 117 | version = "0.1.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | dependencies = [ 120 | "backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 122 | ] 123 | 124 | [[package]] 125 | name = "failure_derive" 126 | version = "0.1.1" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | dependencies = [ 129 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 132 | ] 133 | 134 | [[package]] 135 | name = "foreign-types" 136 | version = "0.2.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | 139 | [[package]] 140 | name = "futures" 141 | version = "0.1.15" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | 144 | [[package]] 145 | name = "gcc" 146 | version = "0.3.54" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | 149 | [[package]] 150 | name = "iovec" 151 | version = "0.1.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | dependencies = [ 154 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 155 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 156 | ] 157 | 158 | [[package]] 159 | name = "itoa" 160 | version = "0.3.4" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | 163 | [[package]] 164 | name = "kernel32-sys" 165 | version = "0.2.2" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | dependencies = [ 168 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 169 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 170 | ] 171 | 172 | [[package]] 173 | name = "lazy_static" 174 | version = "0.2.8" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | 177 | [[package]] 178 | name = "lazycell" 179 | version = "0.5.1" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | 182 | [[package]] 183 | name = "libc" 184 | version = "0.2.30" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | 187 | [[package]] 188 | name = "libsqlite3-sys" 189 | version = "0.8.1" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | dependencies = [ 192 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 193 | "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 194 | ] 195 | 196 | [[package]] 197 | name = "linked-hash-map" 198 | version = "0.4.2" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | 201 | [[package]] 202 | name = "log" 203 | version = "0.3.8" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | 206 | [[package]] 207 | name = "lru-cache" 208 | version = "0.1.1" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | dependencies = [ 211 | "linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 212 | ] 213 | 214 | [[package]] 215 | name = "magenta" 216 | version = "0.1.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | dependencies = [ 219 | "conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 220 | "magenta-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 221 | ] 222 | 223 | [[package]] 224 | name = "magenta-sys" 225 | version = "0.1.1" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | dependencies = [ 228 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 229 | ] 230 | 231 | [[package]] 232 | name = "mio" 233 | version = "0.6.10" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | dependencies = [ 236 | "iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 237 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 238 | "lazycell 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 239 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 240 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 241 | "magenta 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 242 | "magenta-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 243 | "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 244 | "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 245 | "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 246 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 247 | ] 248 | 249 | [[package]] 250 | name = "miow" 251 | version = "0.2.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | dependencies = [ 254 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 255 | "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 256 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 257 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 258 | ] 259 | 260 | [[package]] 261 | name = "net2" 262 | version = "0.2.31" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | dependencies = [ 265 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 266 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 269 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 270 | ] 271 | 272 | [[package]] 273 | name = "num" 274 | version = "0.1.40" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | dependencies = [ 277 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 278 | "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 279 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 280 | ] 281 | 282 | [[package]] 283 | name = "num-integer" 284 | version = "0.1.35" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | dependencies = [ 287 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 288 | ] 289 | 290 | [[package]] 291 | name = "num-iter" 292 | version = "0.1.34" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | dependencies = [ 295 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 296 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 297 | ] 298 | 299 | [[package]] 300 | name = "num-traits" 301 | version = "0.1.40" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | 304 | [[package]] 305 | name = "openssl" 306 | version = "0.9.17" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | dependencies = [ 309 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 310 | "foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 311 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 312 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 313 | "openssl-sys 0.9.17 (registry+https://github.com/rust-lang/crates.io-index)", 314 | ] 315 | 316 | [[package]] 317 | name = "openssl-sys" 318 | version = "0.9.17" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | dependencies = [ 321 | "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", 322 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 323 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 324 | "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 325 | ] 326 | 327 | [[package]] 328 | name = "pkg-config" 329 | version = "0.3.9" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | 332 | [[package]] 333 | name = "quote" 334 | version = "0.3.15" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | 337 | [[package]] 338 | name = "rand" 339 | version = "0.3.16" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | dependencies = [ 342 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 343 | "magenta 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 344 | ] 345 | 346 | [[package]] 347 | name = "redox_syscall" 348 | version = "0.1.31" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | 351 | [[package]] 352 | name = "rmp" 353 | version = "0.8.7" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | dependencies = [ 356 | "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 357 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 358 | ] 359 | 360 | [[package]] 361 | name = "rmp-serde" 362 | version = "0.13.6" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | dependencies = [ 365 | "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 366 | "rmp 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)", 367 | "serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 368 | ] 369 | 370 | [[package]] 371 | name = "rusqlite" 372 | version = "0.12.0" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | dependencies = [ 375 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 376 | "libsqlite3-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 377 | "lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 378 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 379 | ] 380 | 381 | [[package]] 382 | name = "rust-crypto" 383 | version = "0.2.36" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | dependencies = [ 386 | "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", 387 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 388 | "rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 389 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 390 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 391 | ] 392 | 393 | [[package]] 394 | name = "rustc-demangle" 395 | version = "0.1.5" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | 398 | [[package]] 399 | name = "rustc-serialize" 400 | version = "0.3.24" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | 403 | [[package]] 404 | name = "scoped-tls" 405 | version = "0.1.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | 408 | [[package]] 409 | name = "serde" 410 | version = "1.0.14" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | 413 | [[package]] 414 | name = "serde_derive" 415 | version = "1.0.14" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | dependencies = [ 418 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 419 | "serde_derive_internals 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", 420 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 421 | ] 422 | 423 | [[package]] 424 | name = "serde_derive_internals" 425 | version = "0.16.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | dependencies = [ 428 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 429 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 430 | ] 431 | 432 | [[package]] 433 | name = "serde_json" 434 | version = "1.0.3" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | dependencies = [ 437 | "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 438 | "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 439 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 440 | "serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 441 | ] 442 | 443 | [[package]] 444 | name = "server" 445 | version = "0.2.0" 446 | dependencies = [ 447 | "bcrypt 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 448 | "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 449 | "common 0.1.0", 450 | "futures 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 451 | "openssl 0.9.17 (registry+https://github.com/rust-lang/crates.io-index)", 452 | "rusqlite 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", 453 | "serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 454 | "serde_derive 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 455 | "serde_json 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 456 | "tokio-core 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 457 | "tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 458 | "tokio-openssl 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 459 | ] 460 | 461 | [[package]] 462 | name = "slab" 463 | version = "0.3.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | 466 | [[package]] 467 | name = "syn" 468 | version = "0.11.11" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | dependencies = [ 471 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 472 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 473 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 474 | ] 475 | 476 | [[package]] 477 | name = "synom" 478 | version = "0.11.3" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | dependencies = [ 481 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 482 | ] 483 | 484 | [[package]] 485 | name = "synstructure" 486 | version = "0.6.1" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | dependencies = [ 489 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 490 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 491 | ] 492 | 493 | [[package]] 494 | name = "time" 495 | version = "0.1.38" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | dependencies = [ 498 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 499 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 500 | "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", 501 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 502 | ] 503 | 504 | [[package]] 505 | name = "tokio-core" 506 | version = "0.1.9" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | dependencies = [ 509 | "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 510 | "futures 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 511 | "iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 512 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 513 | "mio 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 514 | "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 515 | "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 516 | "tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 517 | ] 518 | 519 | [[package]] 520 | name = "tokio-io" 521 | version = "0.1.3" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | dependencies = [ 524 | "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 525 | "futures 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 526 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 527 | ] 528 | 529 | [[package]] 530 | name = "tokio-openssl" 531 | version = "0.1.3" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | dependencies = [ 534 | "futures 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 535 | "openssl 0.9.17 (registry+https://github.com/rust-lang/crates.io-index)", 536 | "tokio-core 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 537 | "tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 538 | ] 539 | 540 | [[package]] 541 | name = "unicode-xid" 542 | version = "0.0.4" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | 545 | [[package]] 546 | name = "vcpkg" 547 | version = "0.2.2" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | 550 | [[package]] 551 | name = "winapi" 552 | version = "0.2.8" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | 555 | [[package]] 556 | name = "winapi-build" 557 | version = "0.1.1" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | 560 | [[package]] 561 | name = "ws2_32-sys" 562 | version = "0.2.1" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | dependencies = [ 565 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 566 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 567 | ] 568 | 569 | [metadata] 570 | "checksum backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8709cc7ec06f6f0ae6c2c7e12f6ed41540781f72b488d83734978295ceae182e" 571 | "checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" 572 | "checksum bcrypt 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "feed84d558481ece6e7b867df7b9d42c17c6b14220a045ee545382f4386987a9" 573 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 574 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 575 | "checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" 576 | "checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6" 577 | "checksum cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b13a57efd6b30ecd6598ebdb302cca617930b5470647570468a65d12ef9719" 578 | "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" 579 | "checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9" 580 | "checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" 581 | "checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" 582 | "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" 583 | "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" 584 | "checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82" 585 | "checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b" 586 | "checksum foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e4056b9bd47f8ac5ba12be771f77a0dae796d1bbaaf5fd0b9c2d38b69b8a29d" 587 | "checksum futures 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a82bdc62350ca9d7974c760e9665102fc9d740992a528c2254aa930e53b783c4" 588 | "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" 589 | "checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be" 590 | "checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" 591 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 592 | "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" 593 | "checksum lazycell 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3b585b7a6811fb03aa10e74b278a0f00f8dd9b45dc681f148bb29fa5cb61859b" 594 | "checksum libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)" = "2370ca07ec338939e356443dac2296f581453c35fe1e3a3ed06023c49435f915" 595 | "checksum libsqlite3-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "370090ad578ba845a3ad4f383ceb3deba7abd51ab1915ad1f2c982cc6035e31c" 596 | "checksum linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939" 597 | "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" 598 | "checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21" 599 | "checksum magenta 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf0336886480e671965f794bc9b6fce88503563013d1bfb7a502c81fe3ac527" 600 | "checksum magenta-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40d014c7011ac470ae28e2f76a02bfea4a8480f73e701353b49ad7a8d75f4699" 601 | "checksum mio 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "dbd91d3bfbceb13897065e97b2ef177a09a438cb33612b2d371bf568819a9313" 602 | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 603 | "checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09" 604 | "checksum num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "a311b77ebdc5dd4cf6449d81e4135d9f0e3b153839ac90e648a8ef538f923525" 605 | "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" 606 | "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" 607 | "checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0" 608 | "checksum openssl 0.9.17 (registry+https://github.com/rust-lang/crates.io-index)" = "085aaedcc89a2fac1eb2bc19cd66f29d4ea99fec60f82a5f3a88a6be7dbd90b5" 609 | "checksum openssl-sys 0.9.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7e3a9845a4c9fdb321931868aae5549e96bb7b979bf9af7de03603d74691b5f3" 610 | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 611 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 612 | "checksum rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "eb250fd207a4729c976794d03db689c9be1d634ab5a1c9da9492a13d8fecbcdf" 613 | "checksum redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "8dde11f18c108289bef24469638a04dce49da56084f2d50618b226e47eb04509" 614 | "checksum rmp 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a3d45d7afc9b132b34a2479648863aa95c5c88e98b32285326a6ebadc80ec5c9" 615 | "checksum rmp-serde 0.13.6 (registry+https://github.com/rust-lang/crates.io-index)" = "87dcd56d8d769ad197d1010b81723f16faa940bbcfed46fc3f1bdffb80c576ed" 616 | "checksum rusqlite 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffaf393ccdac5580092a4d8eb2edffbffe9a8c4484c62d8a0fcac99bc3718566" 617 | "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" 618 | "checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e" 619 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 620 | "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" 621 | "checksum serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb6a7637a47663ee073391a139ed07851f27ed2532c2abc88c6bf27a16cdf34" 622 | "checksum serde_derive 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "812ff66056fd9a9a5b7c119714243b0862cf98340e7d4b5ee05a932c40d5ea6c" 623 | "checksum serde_derive_internals 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd381f6d01a6616cdba8530492d453b7761b456ba974e98768a18cad2cd76f58" 624 | "checksum serde_json 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d243424e06f9f9c39e3cd36147470fd340db785825e367625f79298a6ac6b7ac" 625 | "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" 626 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 627 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 628 | "checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" 629 | "checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" 630 | "checksum tokio-core 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e85d419699ec4b71bfe35bbc25bb8771e52eff0471a7f75c853ad06e200b4f86" 631 | "checksum tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ab83e7adb5677e42e405fa4ceff75659d93c4d7d7dd22f52fcec59ee9f02af" 632 | "checksum tokio-openssl 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "364cd119c3aec80ebf74e7362e65c4659dbec9fb8fc6c6875c5b28e95bd2b168" 633 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 634 | "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" 635 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 636 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 637 | "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 638 | -------------------------------------------------------------------------------- /client-gtk/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "atk-sys" 3 | version = "0.5.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 11 | ] 12 | 13 | [[package]] 14 | name = "backtrace" 15 | version = "0.3.4" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | dependencies = [ 18 | "backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 19 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 21 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "backtrace-sys" 29 | version = "0.1.16" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | dependencies = [ 32 | "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 34 | ] 35 | 36 | [[package]] 37 | name = "bitflags" 38 | version = "0.9.1" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | 41 | [[package]] 42 | name = "bitflags" 43 | version = "1.0.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | 46 | [[package]] 47 | name = "byteorder" 48 | version = "1.2.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | 51 | [[package]] 52 | name = "c_vec" 53 | version = "1.2.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | 56 | [[package]] 57 | name = "cairo-rs" 58 | version = "0.3.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | dependencies = [ 61 | "c_vec 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 62 | "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "glib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 67 | ] 68 | 69 | [[package]] 70 | name = "cairo-sys-rs" 71 | version = "0.5.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "cc" 81 | version = "1.0.3" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | 84 | [[package]] 85 | name = "cfg-if" 86 | version = "0.1.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | 89 | [[package]] 90 | name = "chrono" 91 | version = "0.4.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | dependencies = [ 94 | "num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "client-gtk" 100 | version = "0.1.0" 101 | dependencies = [ 102 | "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "gtk 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "rusqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "synac 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "dbghelp-sys" 112 | version = "0.2.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | dependencies = [ 115 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 117 | ] 118 | 119 | [[package]] 120 | name = "failure" 121 | version = "0.1.1" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | dependencies = [ 124 | "backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 125 | "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 126 | ] 127 | 128 | [[package]] 129 | name = "failure_derive" 130 | version = "0.1.1" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | dependencies = [ 133 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 136 | ] 137 | 138 | [[package]] 139 | name = "foreign-types" 140 | version = "0.3.2" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | dependencies = [ 143 | "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 144 | ] 145 | 146 | [[package]] 147 | name = "foreign-types-shared" 148 | version = "0.1.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | 151 | [[package]] 152 | name = "gdk" 153 | version = "0.7.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | dependencies = [ 156 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 157 | "cairo-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 158 | "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 159 | "gdk-pixbuf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 160 | "gdk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 161 | "gio 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "glib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "pango 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 167 | ] 168 | 169 | [[package]] 170 | name = "gdk-pixbuf" 171 | version = "0.3.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | dependencies = [ 174 | "gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 175 | "glib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 176 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 177 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 178 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 179 | ] 180 | 181 | [[package]] 182 | name = "gdk-pixbuf-sys" 183 | version = "0.5.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | dependencies = [ 186 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 187 | "gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 188 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 189 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 190 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 191 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 192 | ] 193 | 194 | [[package]] 195 | name = "gdk-sys" 196 | version = "0.5.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | dependencies = [ 199 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 200 | "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 201 | "gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 205 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 206 | "pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 207 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 208 | ] 209 | 210 | [[package]] 211 | name = "gio" 212 | version = "0.3.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | dependencies = [ 215 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 216 | "gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 217 | "glib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 218 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 219 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 220 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 221 | ] 222 | 223 | [[package]] 224 | name = "gio-sys" 225 | version = "0.5.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | dependencies = [ 228 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 229 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 230 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 231 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 232 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 233 | ] 234 | 235 | [[package]] 236 | name = "glib" 237 | version = "0.4.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | dependencies = [ 240 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 241 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 242 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 243 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 244 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 245 | ] 246 | 247 | [[package]] 248 | name = "glib-sys" 249 | version = "0.5.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | dependencies = [ 252 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 253 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 254 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 255 | ] 256 | 257 | [[package]] 258 | name = "gobject-sys" 259 | version = "0.5.0" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | dependencies = [ 262 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 263 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 264 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 265 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 266 | ] 267 | 268 | [[package]] 269 | name = "gtk" 270 | version = "0.3.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | dependencies = [ 273 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 274 | "cairo-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 275 | "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 276 | "gdk 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 277 | "gdk-pixbuf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 278 | "gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 279 | "gdk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 280 | "gio 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 281 | "gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 282 | "glib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 283 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 284 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 285 | "gtk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 286 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "pango 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 288 | ] 289 | 290 | [[package]] 291 | name = "gtk-sys" 292 | version = "0.5.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | dependencies = [ 295 | "atk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 296 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 297 | "cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 298 | "gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 299 | "gdk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 300 | "gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 301 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 302 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 303 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 304 | "pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 305 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 306 | ] 307 | 308 | [[package]] 309 | name = "kernel32-sys" 310 | version = "0.2.2" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | dependencies = [ 313 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 314 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 315 | ] 316 | 317 | [[package]] 318 | name = "lazy_static" 319 | version = "0.2.11" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | 322 | [[package]] 323 | name = "lazy_static" 324 | version = "1.0.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | 327 | [[package]] 328 | name = "libc" 329 | version = "0.2.34" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | 332 | [[package]] 333 | name = "libsqlite3-sys" 334 | version = "0.9.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | dependencies = [ 337 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 338 | "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 339 | ] 340 | 341 | [[package]] 342 | name = "linked-hash-map" 343 | version = "0.4.2" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | 346 | [[package]] 347 | name = "lru-cache" 348 | version = "0.1.1" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | dependencies = [ 351 | "linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 352 | ] 353 | 354 | [[package]] 355 | name = "num" 356 | version = "0.1.41" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | dependencies = [ 359 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 361 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 362 | ] 363 | 364 | [[package]] 365 | name = "num-integer" 366 | version = "0.1.35" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | dependencies = [ 369 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 370 | ] 371 | 372 | [[package]] 373 | name = "num-iter" 374 | version = "0.1.34" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | dependencies = [ 377 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 378 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 379 | ] 380 | 381 | [[package]] 382 | name = "num-traits" 383 | version = "0.1.41" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | 386 | [[package]] 387 | name = "openssl" 388 | version = "0.9.23" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | dependencies = [ 391 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 392 | "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 393 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 394 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 395 | "openssl-sys 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)", 396 | ] 397 | 398 | [[package]] 399 | name = "openssl-sys" 400 | version = "0.9.23" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | dependencies = [ 403 | "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 404 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 405 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 406 | "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 407 | ] 408 | 409 | [[package]] 410 | name = "pango" 411 | version = "0.3.0" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | dependencies = [ 414 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 415 | "glib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 416 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 417 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 418 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 419 | "pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 420 | ] 421 | 422 | [[package]] 423 | name = "pango-sys" 424 | version = "0.5.0" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | dependencies = [ 427 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 428 | "glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 429 | "gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 430 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 431 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 432 | ] 433 | 434 | [[package]] 435 | name = "pkg-config" 436 | version = "0.3.9" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | 439 | [[package]] 440 | name = "quote" 441 | version = "0.3.15" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | 444 | [[package]] 445 | name = "redox_syscall" 446 | version = "0.1.32" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | 449 | [[package]] 450 | name = "rmp" 451 | version = "0.8.7" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | dependencies = [ 454 | "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 455 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 456 | ] 457 | 458 | [[package]] 459 | name = "rmp-serde" 460 | version = "0.13.7" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | dependencies = [ 463 | "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 464 | "rmp 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)", 465 | "serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", 466 | ] 467 | 468 | [[package]] 469 | name = "rusqlite" 470 | version = "0.13.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | dependencies = [ 473 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 474 | "libsqlite3-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 475 | "lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 476 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 477 | ] 478 | 479 | [[package]] 480 | name = "rustc-demangle" 481 | version = "0.1.5" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | 484 | [[package]] 485 | name = "serde" 486 | version = "1.0.24" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | 489 | [[package]] 490 | name = "serde_derive" 491 | version = "1.0.24" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | dependencies = [ 494 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 495 | "serde_derive_internals 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", 496 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 497 | ] 498 | 499 | [[package]] 500 | name = "serde_derive_internals" 501 | version = "0.18.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | dependencies = [ 504 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 505 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 506 | ] 507 | 508 | [[package]] 509 | name = "syn" 510 | version = "0.11.11" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | dependencies = [ 513 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 514 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 515 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 516 | ] 517 | 518 | [[package]] 519 | name = "synac" 520 | version = "0.2.0" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | dependencies = [ 523 | "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 524 | "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 525 | "openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)", 526 | "rmp-serde 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)", 527 | "serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", 528 | "serde_derive 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", 529 | ] 530 | 531 | [[package]] 532 | name = "synom" 533 | version = "0.11.3" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | dependencies = [ 536 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 537 | ] 538 | 539 | [[package]] 540 | name = "synstructure" 541 | version = "0.6.1" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | dependencies = [ 544 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 545 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 546 | ] 547 | 548 | [[package]] 549 | name = "time" 550 | version = "0.1.38" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | dependencies = [ 553 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 554 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 555 | "redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 556 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 557 | ] 558 | 559 | [[package]] 560 | name = "unicode-xid" 561 | version = "0.0.4" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | 564 | [[package]] 565 | name = "vcpkg" 566 | version = "0.2.2" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | 569 | [[package]] 570 | name = "winapi" 571 | version = "0.2.8" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | 574 | [[package]] 575 | name = "winapi-build" 576 | version = "0.1.1" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | 579 | [[package]] 580 | name = "xdg" 581 | version = "2.1.0" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | 584 | [metadata] 585 | "checksum atk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "33a67fd81e1922dddc335887516f2f5254534e89c9d39fa89bca5d79bd150d34" 586 | "checksum backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8709cc7ec06f6f0ae6c2c7e12f6ed41540781f72b488d83734978295ceae182e" 587 | "checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" 588 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 589 | "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" 590 | "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" 591 | "checksum c_vec 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6237ac5a4b1e81c213c24c6437964c61e646df910a914b4ab1487b46df20bd13" 592 | "checksum cairo-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6b5695f59fd036fe5741bc5a4eb20c78fbe42256e3b08a2af26bbcbe8070bf3" 593 | "checksum cairo-sys-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6e18fecaeac51809db57f45f4553cc0975225a7eb435a7a7e91e5e8113a84d" 594 | "checksum cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b13a57efd6b30ecd6598ebdb302cca617930b5470647570468a65d12ef9719" 595 | "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" 596 | "checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9" 597 | "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" 598 | "checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82" 599 | "checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b" 600 | "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 601 | "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 602 | "checksum gdk 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e51db95be6565011bcd5cd99f9b17fdd585001057a999b21e09f1e8c28deb9" 603 | "checksum gdk-pixbuf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "16160d212ae91abe9f3324c3fb233929ba322dde63585d15cda3336f8c529ed1" 604 | "checksum gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "798f97101eea8180da363d0e80e07ec7ec6d1809306601c0100c1de5bc8b4f52" 605 | "checksum gdk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4ee916f5f25c5f4b21bd9dcb12a216ae697406940ff9476358c308a8ececada" 606 | "checksum gio 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "84ba5a2beb559059a0c9c2bd3681743cdede8d9a36c775840bca800333b22867" 607 | "checksum gio-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a303bbf7a5e75ab3b627117ff10e495d1b9e97e1d68966285ac2b1f6270091bc" 608 | "checksum glib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "450247060df7d52fdad31e1d66f30d967e925c9d1d26a0ae050cfe33dcd00d08" 609 | "checksum glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9693049613ff52b93013cc3d2590366d8e530366d288438724b73f6c7dc4be8" 610 | "checksum gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60d507c87a71b1143c66ed21a969be9b99a76df234b342d733e787e6c9c7d7c2" 611 | "checksum gtk 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0847c507e52c1feaede13ef56fb4847742438602655449d5f1f782e8633f146f" 612 | "checksum gtk-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "905fcfbaaad1b44ec0b4bba9e4d527d728284c62bc2ba41fccedace2b096766f" 613 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 614 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 615 | "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" 616 | "checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0" 617 | "checksum libsqlite3-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e9eb7b8e152b6a01be6a4a2917248381875758250dc3df5d46caf9250341dda" 618 | "checksum linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939" 619 | "checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21" 620 | "checksum num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cc4083e14b542ea3eb9b5f33ff48bd373a92d78687e74f4cc0a30caeb754f0ca" 621 | "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" 622 | "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" 623 | "checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" 624 | "checksum openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)" = "169a4b9160baf9b9b1ab975418c673686638995ba921683a7f1e01470dcb8854" 625 | "checksum openssl-sys 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)" = "2200ffec628e3f14c39fc0131a301db214f1a7d584e36507ee8700b0c7fb7a46" 626 | "checksum pango 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e81c404ab81ea7ea2fc2431a0a7672507b80e4b8bf4b41eac3fc83cc665104e" 627 | "checksum pango-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34f34a1be107fe16abb2744e0e206bee4b3b07460b5fddd3009a6aaf60bd69ab" 628 | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 629 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 630 | "checksum redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ab105df655884ede59d45b7070c8a65002d921461ee813a024558ca16030eea0" 631 | "checksum rmp 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a3d45d7afc9b132b34a2479648863aa95c5c88e98b32285326a6ebadc80ec5c9" 632 | "checksum rmp-serde 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)" = "011e1d58446e9fa3af7cdc1fb91295b10621d3ac4cb3a85cc86385ee9ca50cd3" 633 | "checksum rusqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9409d78a5a9646685688266e1833df8f08b71ffcae1b5db6c1bfb5970d8a80f" 634 | "checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e" 635 | "checksum serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1c57ab4ec5fa85d08aaf8ed9245899d9bbdd66768945b21113b84d5f595cb6a1" 636 | "checksum serde_derive 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "02c92ea07b6e49b959c1481804ebc9bfd92d3c459f1274c9a9546829e42a66ce" 637 | "checksum serde_derive_internals 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75c6aac7b99801a16db5b40b7bf0d7e4ba16e76fbf231e32a4677f271cac0603" 638 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 639 | "checksum synac 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b69063c8b5c0878742e95ebac8c627d74953c1f4e75bb1c4b4f31b4476647f4d" 640 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 641 | "checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" 642 | "checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" 643 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 644 | "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" 645 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 646 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 647 | "checksum xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a66b7c2281ebde13cf4391d70d4c7e5946c3c25e72a7b859ca8f677dcd0b0c61" 648 | -------------------------------------------------------------------------------- /client/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate openssl; 2 | extern crate rusqlite; 3 | extern crate rustyline; 4 | extern crate synac; 5 | 6 | use synac::State; 7 | use synac::common::{self, Packet}; 8 | use rusqlite::Connection as SqlConnection; 9 | use std::collections::HashMap; 10 | use std::env; 11 | use std::fs; 12 | use std::net::SocketAddr; 13 | use std::path::PathBuf; 14 | use std::sync::mpsc; 15 | use std::sync::{Arc, Mutex, RwLock}; 16 | use std::thread; 17 | use std::time::{Duration, Instant}; 18 | 19 | mod connect; 20 | mod frontend; 21 | mod help; 22 | mod listener; 23 | mod parser; 24 | 25 | #[derive(PartialEq, Eq, Clone, Copy)] 26 | pub enum LogEntryId { 27 | Message(usize), 28 | None, 29 | Sending 30 | } 31 | 32 | pub struct Connector { 33 | db: Arc>, 34 | nick: RwLock, 35 | } 36 | pub struct Session { 37 | inner: synac::Session, 38 | state: synac::State, 39 | 40 | addr: SocketAddr, 41 | channel: Option, 42 | id: usize, 43 | last: Option<(usize, Vec)>, 44 | typing: HashMap<(usize, usize), Instant> 45 | } 46 | impl Session { 47 | pub fn new(addr: SocketAddr, id: usize, inner: synac::Session) -> Session { 48 | Session { 49 | inner: inner, 50 | state: State::new(), 51 | 52 | addr: addr, 53 | channel: None, 54 | id: id, 55 | last: None, 56 | typing: HashMap::new(), 57 | } 58 | } 59 | } 60 | 61 | fn main() { 62 | let session: Arc>> = Arc::new(Mutex::new(None)); 63 | let screen = Arc::new(frontend::Screen::new(&session)); 64 | 65 | // See https://github.com/rust-lang/rust/issues/35853 66 | macro_rules! println { 67 | () => { screen.log(String::new()); }; 68 | ($arg:expr) => { screen.log(String::from($arg)); }; 69 | ($($arg:expr),*) => { screen.log(format!($($arg),*)); }; 70 | } 71 | macro_rules! readline { 72 | ($break:block) => { 73 | match screen.readline() { 74 | Ok(ok) => ok, 75 | Err(_) => $break 76 | } 77 | } 78 | } 79 | macro_rules! readpass { 80 | ($break:block) => { 81 | match screen.readpass() { 82 | Ok(ok) => ok, 83 | Err(_) => $break 84 | } 85 | } 86 | } 87 | 88 | #[cfg(unix)] 89 | let path = env::var("XDG_CONFIG_HOME").ok().map(|item| PathBuf::from(item)).or_else(|| { 90 | env::home_dir().map(|mut home| { 91 | home.push(".config"); 92 | home 93 | }) 94 | }).map(|mut path| { 95 | path.push("synac"); 96 | path 97 | }); 98 | #[cfg(not(unix))] 99 | let path = None; 100 | 101 | let mut path = path.unwrap_or_else(PathBuf::new); 102 | if let Err(err) = fs::create_dir_all(&path) { 103 | eprintln!("Failed to create all directories"); 104 | eprintln!("{}", err); 105 | return; 106 | } 107 | path.push("data.sqlite"); 108 | let db = match SqlConnection::open(&path) { 109 | Ok(ok) => ok, 110 | Err(err) => { 111 | eprintln!("Failed to open database"); 112 | eprintln!("{}", err); 113 | return; 114 | } 115 | }; 116 | db.execute("CREATE TABLE IF NOT EXISTS data ( 117 | key TEXT NOT NULL UNIQUE, 118 | value TEXT NOT NULL 119 | )", &[]) 120 | .expect("Couldn't create SQLite table"); 121 | db.execute("CREATE TABLE IF NOT EXISTS pms ( 122 | private BLOB NOT NULL, 123 | public BLOB NOT NULL, 124 | recipient INTEGER NOT NULL PRIMARY KEY 125 | )", &[]) 126 | .expect("Couldn't create SQLite table"); 127 | db.execute("CREATE TABLE IF NOT EXISTS servers ( 128 | ip TEXT NOT NULL PRIMARY KEY, 129 | key BLOB NOT NULL, 130 | token TEXT 131 | )", &[]) 132 | .expect("Couldn't create SQLite table"); 133 | 134 | let nick = { 135 | let mut stmt = db.prepare("SELECT value FROM data WHERE key = 'nick'").unwrap(); 136 | let mut rows = stmt.query(&[]).unwrap(); 137 | 138 | if let Some(row) = rows.next() { 139 | row.unwrap().get::<_, String>(0) 140 | } else { 141 | #[cfg(unix)] 142 | { env::var("USER").unwrap_or_else(|_| String::from("unknown")) } 143 | #[cfg(windows)] 144 | { env::var("USERNAME").unwrap_or_else(|_| String::from("unknown")) } 145 | #[cfg(not(any(unix, windows)))] 146 | { String::from("unknown") } 147 | } 148 | }; 149 | 150 | let db = Arc::new(Mutex::new(db)); 151 | 152 | println!("Welcome, {}", nick); 153 | println!("To quit, type /quit"); 154 | println!("To change your name, type"); 155 | println!("/nick "); 156 | println!("To connect to a server, type"); 157 | println!("/connect "); 158 | println!(); 159 | 160 | let connector = Arc::new(Connector { 161 | db: Arc::clone(&db), 162 | nick: RwLock::new(nick) 163 | }); 164 | 165 | macro_rules! write { 166 | ($session:expr, $packet:expr, $break:block) => { 167 | if !connect::write(&connector, &$packet, &*screen, $session) { 168 | $break 169 | } 170 | } 171 | } 172 | 173 | let (tx_sent, rx_sent) = mpsc::sync_channel(0); 174 | let (tx_stop, rx_stop) = mpsc::channel(); 175 | 176 | let db_clone = Arc::clone(&db); 177 | let screen_clone = Arc::clone(&screen); 178 | let session_clone = Arc::clone(&session); 179 | let thread = thread::spawn(move || { 180 | listener::listen(db_clone, screen_clone, tx_sent, session_clone, rx_stop); 181 | }); 182 | 183 | loop { 184 | screen.repaint(); 185 | let input = readline!({ break; }); 186 | if input.is_empty() { 187 | continue; 188 | } 189 | 190 | macro_rules! require_session { 191 | ($session:expr) => { 192 | match *$session { 193 | Some(ref mut s) => s, 194 | None => { 195 | println!("You're not connected to a server"); 196 | continue; 197 | } 198 | } 199 | } 200 | } 201 | 202 | if input.starts_with('/') { 203 | let mut args = parser::parse(&input[1..]); 204 | if args.is_empty() { 205 | continue; 206 | } 207 | let command = args.remove(0); 208 | 209 | macro_rules! usage_min { 210 | ($amount:expr, $usage:expr) => { 211 | if args.len() < $amount { 212 | println!(concat!("Usage: /", $usage)); 213 | continue; 214 | } 215 | } 216 | } 217 | macro_rules! usage_max { 218 | ($amount:expr, $usage:expr) => { 219 | if args.len() > $amount { 220 | println!(concat!("Usage: /", $usage)); 221 | continue; 222 | } 223 | } 224 | } 225 | macro_rules! usage { 226 | ($amount:expr, $usage:expr) => { 227 | if args.len() != $amount { 228 | println!(concat!("Usage: /", $usage)); 229 | continue; 230 | } 231 | } 232 | } 233 | 234 | match &*command { 235 | "ban" | "unban" => { 236 | usage!(1, "ban/unban "); 237 | let mut session = session.lock().unwrap(); 238 | let session = require_session!(session); 239 | let id = match find_user(&session.state.users, &args[0]) { 240 | Some(user) => user.id, 241 | None => { 242 | println!("No such user"); 243 | continue; 244 | } 245 | }; 246 | 247 | let packet = Packet::UserUpdate(common::UserUpdate { 248 | ban: Some(command == "ban"), 249 | groups: None, 250 | id: id 251 | }); 252 | write!(session, packet, {}) 253 | }, 254 | "connect" => { 255 | usage!(1, "connect "); 256 | let mut session = session.lock().unwrap(); 257 | if session.is_some() { 258 | println!("You have to disconnect before doing that."); 259 | continue; 260 | } 261 | 262 | let addr = match parse_addr(&args[0]) { 263 | Some(some) => some, 264 | None => { 265 | println!("Could not parse IP"); 266 | continue; 267 | } 268 | }; 269 | *session = connect::connect(addr, &connector, &screen); 270 | }, 271 | "create" => { 272 | usage_min!(2, "create <\"channel\"/\"group\"> [data]"); 273 | let mut session = session.lock().unwrap(); 274 | let session = require_session!(session); 275 | let packet = match &*args[0] { 276 | "channel" => { 277 | usage_max!(2, "create channel "); 278 | let mut name = args.remove(1); 279 | if name.starts_with('#') { 280 | name.drain(..1); 281 | } 282 | Packet::ChannelCreate(common::ChannelCreate { 283 | overrides: HashMap::new(), 284 | name: name 285 | }) 286 | }, 287 | "group" => { 288 | usage_max!(3, "create group [data]"); 289 | let (mut allow, mut deny) = (0, 0); 290 | if args.len() == 3 && !from_perm_string(&*args[2], &mut allow, &mut deny) { 291 | println!("Invalid permission string"); 292 | continue; 293 | } 294 | let max = session.state.groups.values().max_by_key(|item| item.pos); 295 | Packet::GroupCreate(common::GroupCreate { 296 | allow: allow, 297 | deny: deny, 298 | name: args.remove(1), 299 | pos: max.map_or(1, |item| item.pos+1), 300 | unassignable: false 301 | }) 302 | }, 303 | _ => { println!("Unable to create that"); continue; } 304 | }; 305 | write!(session, packet, {}) 306 | }, 307 | "delete" => { 308 | usage!(2, "delete <\"channel\"/\"group\"/\"message\"> "); 309 | 310 | let mut session = session.lock().unwrap(); 311 | let session = require_session!(session); 312 | let id = match args[1].parse() { 313 | Ok(ok) => ok, 314 | Err(_) => { 315 | println!("Failed to parse ID"); 316 | continue; 317 | } 318 | }; 319 | 320 | let packet = match &*args[0] { 321 | "group" => if session.state.groups.contains_key(&id) { 322 | Some(Packet::GroupDelete(common::GroupDelete { 323 | id: id 324 | })) 325 | } else { None }, 326 | "channel" => if session.state.channels.contains_key(&id) { 327 | Some(Packet::ChannelDelete(common::ChannelDelete { 328 | id: id 329 | })) 330 | } else { None }, 331 | "message" => 332 | Some(Packet::MessageDelete(common::MessageDelete { 333 | id: id 334 | })), 335 | _ => { 336 | println!("Unable to delete that"); 337 | continue; 338 | } 339 | }; 340 | if let Some(packet) = packet { 341 | write!(session, packet, {}) 342 | } else { 343 | println!("Nothing with that ID exists"); 344 | } 345 | }, 346 | "disconnect" => { 347 | usage!(0, "disconnect"); 348 | let mut session = session.lock().unwrap(); 349 | { 350 | let session = require_session!(session); 351 | let _ = session.inner.send(&Packet::Close); 352 | } 353 | *session = None; 354 | }, 355 | "forget" => { 356 | usage!(1, "forget "); 357 | let addr = match parse_addr(&args[0]) { 358 | Some(some) => some, 359 | None => { 360 | println!("Invalid IP"); 361 | continue; 362 | } 363 | }; 364 | db.lock().unwrap().execute("DELETE FROM servers WHERE ip = ?", &[&addr.to_string()]).unwrap(); 365 | }, 366 | "help" => { 367 | let borrowed: Vec<_> = args.iter().map(|arg| &**arg).collect(); 368 | help::help(&*borrowed, &screen); 369 | }, 370 | "info" => { 371 | usage!(1, "info "); 372 | let mut session = session.lock().unwrap(); 373 | let session = require_session!(session); 374 | let mut name = &*args[0]; 375 | if name.starts_with('#') { 376 | name = &name[1..]; 377 | } 378 | 379 | let id = name.parse(); 380 | 381 | println!(); 382 | for channel in session.state.channels.values() { 383 | if name == channel.name 384 | || (name.starts_with('#') && name[1..] == channel.name) 385 | || Ok(channel.id) == id { 386 | println!("Channel #{}", channel.name); 387 | println!("ID: #{}", channel.id); 388 | for (id, &(allow, deny)) in &channel.overrides { 389 | println!("Permission override: Role #{} = {}", id, to_perm_string(allow, deny)); 390 | } 391 | } 392 | } 393 | for group in session.state.groups.values() { 394 | if name == group.name || Ok(group.id) == id { 395 | println!("Group {}", group.name); 396 | println!("Permission: {}", to_perm_string(group.allow, group.deny)); 397 | println!("ID: #{}", group.id); 398 | println!("Position: {}", group.pos); 399 | } 400 | } 401 | for user in session.state.users.values() { 402 | if name == user.name || Ok(user.id) == id { 403 | println!("User {}", user.name); 404 | println!( 405 | "Groups: [{}]", 406 | user.groups.iter() 407 | .fold(String::new(), |mut acc, id| { 408 | if !acc.is_empty() { 409 | acc.push_str(", "); 410 | } 411 | acc.push_str(&id.to_string()); 412 | acc 413 | }) 414 | ); 415 | if user.ban { 416 | println!("Banned."); 417 | } 418 | println!("Bot: {}", if user.bot { "true" } else { "false" }); 419 | println!("ID: #{}", user.id); 420 | } 421 | } 422 | }, 423 | "join" => { 424 | usage!(1, "join "); 425 | let mut session = session.lock().unwrap(); 426 | let session = require_session!(session); 427 | let mut name = &*args[0]; 428 | if name.starts_with('#') { 429 | name = &name[1..]; 430 | } 431 | 432 | let mut packet = None; 433 | for channel in session.state.channels.values() { 434 | if channel.name == name { 435 | session.channel = Some(channel.id); 436 | screen.clear(); 437 | println!("Joined channel #{}", channel.name); 438 | packet = Some(Packet::MessageList(common::MessageList { 439 | after: None, 440 | before: None, 441 | channel: channel.id, 442 | limit: common::LIMIT_BULK 443 | })); 444 | break; 445 | } 446 | } 447 | if let Some(packet) = packet { 448 | write!(session, packet, {}); 449 | } else { 450 | println!("No channel found with that name"); 451 | } 452 | }, 453 | "list" => { 454 | usage!(1, "list <\"channels\"/\"groups\"/\"users\">"); 455 | let mut session = session.lock().unwrap(); 456 | let session = require_session!(session); 457 | match &*args[0] { 458 | "channels" => { 459 | // Yeah, cloning this is bad... But what can I do? I need to sort! 460 | // Though, I suspect this might be a shallow copy. 461 | let mut channels: Vec<_> = session.state.channels.values().collect(); 462 | channels.sort_by_key(|item| &item.name); 463 | 464 | let result = channels.iter().fold(String::new(), |mut acc, channel| { 465 | if !acc.is_empty() { acc.push_str(", "); } 466 | acc.push('#'); 467 | acc.push_str(&channel.name); 468 | acc 469 | }); 470 | println!(result); 471 | }, 472 | "groups" => { 473 | // Read the above comment, thank you ---------------------------^ 474 | let mut groups: Vec<_> = session.state.groups.values().collect(); 475 | groups.sort_by_key(|item| &item.pos); 476 | 477 | let result = groups.iter().fold(String::new(), |mut acc, group| { 478 | if !acc.is_empty() { acc.push_str(", "); } 479 | acc.push_str(&group.name); 480 | acc 481 | }); 482 | println!(result); 483 | }, 484 | "users" => { 485 | // something something above comment 486 | let mut users: Vec<_> = session.state.users.values().collect(); 487 | users.sort_by_key(|item| &item.name); 488 | 489 | for banned in &[false, true] { 490 | if *banned { 491 | println!("Banned:"); 492 | } 493 | println!(users.iter().fold(String::new(), |mut acc, user| { 494 | if user.ban == *banned { 495 | if !acc.is_empty() { acc.push_str(", "); } 496 | acc.push_str(&user.name); 497 | } 498 | acc 499 | })); 500 | } 501 | }, 502 | _ => println!("Unable to list that") 503 | } 504 | }, 505 | "msg" => { 506 | usage!(2, "msg "); 507 | let mut session = session.lock().unwrap(); 508 | let session = require_session!(session); 509 | 510 | let packet = { 511 | let user = match find_user(&session.state.users, &args[0]) { 512 | Some(user) => user, 513 | None => { 514 | println!("No such user"); 515 | continue; 516 | } 517 | }; 518 | 519 | let public = { 520 | let db = db.lock().unwrap(); 521 | let mut stmt = db.prepare_cached("SELECT public FROM pms WHERE recipient = ?").unwrap(); 522 | let mut rows = stmt.query(&[&(user.id as i64)]).unwrap(); 523 | 524 | if let Some(row) = rows.next() { 525 | let row = row.unwrap(); 526 | row.get::<_, String>(0) 527 | } else { 528 | println!("Please run `/setupkeys {}` first", user.name); 529 | continue; 530 | } 531 | }; 532 | use openssl::rsa::Rsa; 533 | let rsa = match Rsa::public_key_from_pem(public.as_bytes()) { 534 | Ok(ok) => ok, 535 | Err(err) => { 536 | println!("Error! Is that valid PEM data?"); 537 | println!("Details: {}", err); 538 | continue; 539 | } 540 | }; 541 | Packet::PrivateMessage(common::PrivateMessage { 542 | text: match synac::encrypt(args[1].as_bytes(), &rsa) { 543 | Ok(ok) => ok, 544 | Err(err) => { 545 | println!("Error! Failed to encrypt! D:"); 546 | println!("{}", err); 547 | continue; 548 | } 549 | }, 550 | recipient: user.id 551 | }) 552 | }; 553 | write!(session, packet, {}); 554 | println!( 555 | "You privately messaged {}: {}", 556 | args[0], 557 | args[1] 558 | ); 559 | }, 560 | "nick" => { 561 | usage!(1, "nick "); 562 | let new = args.remove(0); 563 | 564 | db.lock().unwrap().execute( 565 | "REPLACE INTO data (key, value) VALUES ('nick', ?)", 566 | &[&new] 567 | ).unwrap(); 568 | 569 | let mut session = session.lock().unwrap(); 570 | if let Some(ref mut session) = *session { 571 | let packet = Packet::LoginUpdate(common::LoginUpdate { 572 | name: Some(new.clone()), 573 | password_current: None, 574 | password_new: None, 575 | reset_token: false 576 | }); 577 | write!(session, packet, {}); 578 | } 579 | 580 | println!("Your name is now {}", new); 581 | *connector.nick.write().unwrap() = new; 582 | }, 583 | "passwd" => { 584 | usage!(0, "passwd"); 585 | { 586 | let mut session = session.lock().unwrap(); 587 | let session = require_session!(session); 588 | 589 | println!("Current password: "); 590 | let current = readpass!({ continue; }); 591 | println!("New password: "); 592 | let new = readpass!({ continue; }); 593 | 594 | let packet = Packet::LoginUpdate(common::LoginUpdate { 595 | name: None, 596 | password_current: Some(current), 597 | password_new: Some(new), 598 | reset_token: true // Doesn't actually matter in this case 599 | }); 600 | write!(session, packet, { continue; }) 601 | } 602 | let _ = rx_sent.recv_timeout(Duration::from_secs(10)); 603 | }, 604 | "quit" => break, 605 | "setupkeys" => { 606 | usage!(1, "setupkeys "); 607 | 608 | let mut session = session.lock().unwrap(); 609 | let session = require_session!(session); 610 | 611 | let id = match find_user(&session.state.users, &args[0]) { 612 | Some(user) => user.id, 613 | None => { 614 | println!("No such user"); 615 | continue; 616 | } 617 | }; 618 | 619 | println!("In order to secure your connection, we need two things:"); 620 | use openssl::rsa::Rsa; 621 | let rsa = match Rsa::generate(common::RSA_LENGTH) { 622 | Ok(ok) => ok, 623 | Err(err) => { 624 | println!("Error! Failed to generate a key pair."); 625 | println!("Details: {}", err); 626 | continue; 627 | } 628 | }; 629 | let private = rsa.private_key_to_pem().unwrap(); 630 | let public = rsa.public_key_to_pem().unwrap(); 631 | 632 | println!("1. You need to give your recipient this \"public key\":"); 633 | println!(String::from_utf8_lossy(&public)); 634 | println!("2. Have your recipient run /setupkeys too."); 635 | println!("You will be asked to enter his/her public key here."); 636 | println!("When you've entered it, type \"END\"."); 637 | println!("Enter his/her public key here:"); 638 | 639 | let mut key = String::with_capacity(512); // idk, just guessing 640 | while let Ok(line) = screen.readline() { 641 | if line == "END" { break; } 642 | 643 | key.push_str(&line); 644 | key.push('\n'); 645 | } 646 | 647 | if let Err(err) = Rsa::public_key_from_pem(key.as_bytes()) { 648 | println!("Error! Did you enter it correctly?"); 649 | println!("Details: {}", err); 650 | continue; 651 | } 652 | db.lock().unwrap().execute( 653 | "REPLACE INTO pms (private, public, recipient) VALUES (?, ?, ?)", 654 | &[&private, &key, &(id as i64)] 655 | ).unwrap(); 656 | }, 657 | "update" => { 658 | usage!(2, "update <\"channel\"/\"group\"> "); 659 | 660 | let mut session = session.lock().unwrap(); 661 | let session = require_session!(session); 662 | let id = match args[1].parse() { 663 | Ok(ok) => ok, 664 | Err(_) => { 665 | println!("Not a valid number"); 666 | continue; 667 | } 668 | }; 669 | 670 | let packet = match &*args[0] { 671 | "group" => if let Some(group) = session.state.groups.get(&id) { 672 | let (mut allow, mut deny) = (group.allow, group.deny); 673 | 674 | println!("Editing: {}", group.name); 675 | println!("(Press enter to keep current value)"); 676 | println!(); 677 | println!("Name [{}]: ", group.name); 678 | 679 | let name = readline!({ continue; }); 680 | let mut name = name.trim(); 681 | if name.is_empty() { name = &group.name }; 682 | 683 | println!("Permission [{}]: ", to_perm_string(allow, deny)); 684 | let perms = readline!({ continue; }); 685 | if !from_perm_string(&perms, &mut allow, &mut deny) { 686 | println!("Invalid permission string"); 687 | continue; 688 | } 689 | 690 | println!("Position [{}]: ", group.pos); 691 | let pos = readline!({ continue; }); 692 | let pos = pos.trim(); 693 | let pos = if pos.is_empty() { 694 | group.pos 695 | } else { 696 | match pos.parse() { 697 | Ok(ok) => ok, 698 | Err(_) => { 699 | println!("Not a valid number"); 700 | continue; 701 | } 702 | } 703 | }; 704 | 705 | Some(Packet::GroupUpdate(common::GroupUpdate { 706 | inner: common::Group { 707 | allow: allow, 708 | deny: deny, 709 | id: group.id, 710 | name: name.to_string(), 711 | pos: pos, 712 | unassignable: group.unassignable 713 | } 714 | })) 715 | } else { None }, 716 | "channel" => if let Some(channel) = session.state.channels.get(&id) { 717 | println!("Editing: #{}", channel.name); 718 | println!("(Press enter to keep current value)"); 719 | println!(); 720 | 721 | println!("Name [{}]: ", channel.name); 722 | let name = readline!({ continue; }); 723 | let mut name = name.trim(); 724 | if name.is_empty() { name = &channel.name } 725 | 726 | let overrides = match screen.get_channel_overrides(channel.overrides.clone(), session) { 727 | Ok(ok) => ok, 728 | Err(_) => continue 729 | }; 730 | 731 | Some(Packet::ChannelUpdate(common::ChannelUpdate { 732 | inner: common::Channel { 733 | id: channel.id, 734 | name: name.to_string(), 735 | overrides: overrides 736 | }, 737 | keep_overrides: false 738 | })) 739 | } else { None }, 740 | "user" => if let Some(user) = session.state.users.get(&id) { 741 | let groups = match screen.get_user_groups(user.groups.clone(), session) { 742 | Ok(ok) => ok, 743 | Err(_) => continue 744 | }; 745 | Some(Packet::UserUpdate(common::UserUpdate { 746 | ban: None, 747 | groups: Some(groups), 748 | id: id 749 | })) 750 | } else { None }, 751 | _ => { 752 | println!("Unable to delete that"); 753 | continue; 754 | } 755 | }; 756 | if let Some(packet) = packet { 757 | write!(session, packet, {}) 758 | } else { 759 | println!("Nothing with that ID exists"); 760 | } 761 | }, 762 | _ => { 763 | println!("Unknown command"); 764 | } 765 | } 766 | continue; 767 | } else if input.starts_with('!') { 768 | let mut session = session.lock().unwrap(); 769 | let session = require_session!(session); 770 | let mut args = parser::parse(&input[1..]); 771 | if args.len() < 2 { 772 | continue; 773 | } 774 | let name = args.remove(0); 775 | 776 | let recipient = match find_user(&session.state.users, &name) { 777 | Some(user) if user.bot => user.id, 778 | Some(_) => { println!("That's not a bot!"); continue; }, 779 | None => { println!("No such user"); continue; } 780 | }; 781 | 782 | let packet = Packet::Command(common::Command { 783 | args: args, 784 | recipient: recipient 785 | }); 786 | 787 | write!(session, packet, {}); 788 | 789 | continue; 790 | } 791 | 792 | let mut session = session.lock().unwrap(); 793 | let session = require_session!(session); 794 | 795 | if let Some(channel) = session.channel { 796 | let packet = if input.starts_with("s/") && session.last.is_some() { 797 | let mut parts = input[2..].splitn(2, '/'); 798 | let find = match parts.next() { 799 | Some(some) => some, 800 | None => continue 801 | }; 802 | let replace = match parts.next() { 803 | Some(some) => some, 804 | None => continue 805 | }; 806 | let last = session.last.as_ref().unwrap(); 807 | Packet::MessageUpdate(common::MessageUpdate { 808 | id: last.0, 809 | text: String::from_utf8_lossy(&last.1).replace(find, replace).into_bytes() 810 | }) 811 | } else { 812 | screen.log_with_id(format!("{}: {}", connector.nick.read().unwrap(), input), LogEntryId::Sending); 813 | Packet::MessageCreate(common::MessageCreate { 814 | channel: channel, 815 | text: input.into_bytes() 816 | }) 817 | }; 818 | 819 | write!(session, packet, {}) 820 | } else { 821 | println!("No channel specified. See /create channel, /list channels and /join"); 822 | continue; 823 | } 824 | } 825 | 826 | tx_stop.send(()).unwrap(); 827 | if let Some(ref mut session) = *session.lock().unwrap() { 828 | let _ = session.inner.send(&Packet::Close); 829 | } 830 | thread.join().unwrap(); 831 | } 832 | 833 | fn find_user<'a>(users: &'a HashMap, name: &str) -> Option<&'a common::User> { 834 | users.values().find(|user| user.name == name) 835 | } 836 | fn to_perm_string(allow: u8, deny: u8) -> String { 837 | let mut result = String::with_capacity(10); 838 | 839 | if allow != 0 { 840 | result.push('+'); 841 | result.push_str(&to_single_perm_string(allow)); 842 | } 843 | if deny != 0 { 844 | result.push('-'); 845 | result.push_str(&to_single_perm_string(deny)); 846 | } 847 | 848 | result.shrink_to_fit(); 849 | result 850 | } 851 | fn to_single_perm_string(bitmask: u8) -> String { 852 | let mut result = String::with_capacity(4); 853 | 854 | if bitmask & common::PERM_READ == common::PERM_READ { 855 | result.push('r'); 856 | } 857 | if bitmask & common::PERM_WRITE == common::PERM_WRITE { 858 | result.push('w'); 859 | } 860 | if bitmask & common::PERM_ASSIGN_GROUPS == common::PERM_ASSIGN_GROUPS { 861 | result.push('s'); 862 | } 863 | if bitmask & common::PERM_MANAGE_GROUPS == common::PERM_MANAGE_GROUPS { 864 | result.push('a'); 865 | } 866 | if bitmask & common::PERM_MANAGE_CHANNELS == common::PERM_MANAGE_CHANNELS { 867 | result.push('c'); 868 | } 869 | if bitmask & common::PERM_MANAGE_MESSAGES == common::PERM_MANAGE_MESSAGES { 870 | result.push('m'); 871 | } 872 | 873 | result 874 | } 875 | fn from_perm_string(input: &str, allow: &mut u8, deny: &mut u8) -> bool { 876 | let mut mode = '+'; 877 | 878 | for c in input.chars() { 879 | if c == '+' || c == '-' || c == '=' { 880 | mode = c; 881 | continue; 882 | } 883 | let perm = match c { 884 | 'r' => common::PERM_READ, 885 | 'w' => common::PERM_WRITE, 886 | 887 | 's' => common::PERM_ASSIGN_GROUPS, 888 | 'c' => common::PERM_MANAGE_CHANNELS, 889 | 'g' => common::PERM_MANAGE_GROUPS, 890 | 'm' => common::PERM_MANAGE_MESSAGES, 891 | ' ' => continue, 892 | _ => return false 893 | }; 894 | match mode { 895 | '+' => { *allow |= perm; *deny &= !perm; }, 896 | '-' => { *allow &= !perm; *deny |= perm }, 897 | '=' => { *allow &= !perm; *deny &= !perm } 898 | _ => unreachable!() 899 | } 900 | } 901 | 902 | true 903 | } 904 | 905 | fn parse_addr(input: &str) -> Option { 906 | let mut parts = input.rsplitn(2, ':'); 907 | let addr = match (parts.next()?, parts.next()) { 908 | (port, Some(ip)) => (ip, port.parse().ok()?), 909 | (ip, None) => (ip, common::DEFAULT_PORT) 910 | }; 911 | 912 | use std::net::ToSocketAddrs; 913 | addr.to_socket_addrs() 914 | .ok() 915 | .and_then(|mut addrs| addrs.next()) 916 | } 917 | 918 | fn get_typing_string(mut people: I, len: usize) -> String 919 | where I: Iterator, 920 | V: AsRef { 921 | macro_rules! next { 922 | () => { people.next().unwrap().as_ref() } 923 | } 924 | match len { 925 | n if n > 500 => String::from("(╯°□°)╯︵ ┻━┻"), 926 | n if n > 100 => String::from("A crap ton of people are typing"), 927 | n if n > 50 => String::from("Over 50 people are typing"), 928 | n if n > 10 => String::from("Over 10 people are typing"), 929 | n if n > 3 => String::from("Several people are typing"), 930 | 3 => format!("{}, {} and {} are typing", next!(), next!(), next!()), 931 | 2 => format!("{} and {} are typing", next!(), next!()), 932 | 1 => format!("{} is typing", next!()), 933 | _ => String::new() 934 | } 935 | } 936 | --------------------------------------------------------------------------------