use crate::telepathy; use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection}; use dbus::tree; use dc::config::Config; use dc::context::Context; use dc::Event; use deltachat as dc; use std::collections::HashMap; use std::sync::{Arc, RwLock}; use std::thread; use std::time::Duration; pub const CONN_BUS_NAME: &'static str = "org.freedesktop.Telepathy.Connection.padfoot.delta"; pub const CONN_OBJECT_PATH: &'static str = "/org/freedesktop/Telepathy/Connection/padfoot/delta"; #[derive(Debug)] pub struct Connection { id: String, ctx: Arc>, } fn escape_one(b: u8) -> String { format!("_{:0<2x}", b) } // Some non-empty sequence of ASCII letters, digits and underscores fn escape(s: String) -> String { // Special-case the empty string if s.len() == 0 { return "_".to_string(); } let bytes = s.into_bytes(); let mut iter = bytes.iter(); let mut out = String::new(); // Only alphanumeric in the first byte let x = *iter.next().expect("Already checked len > 0"); let first = match x { b'a'..=b'z' | b'A'..=b'Z' => unsafe { String::from_utf8_unchecked(vec![x]) }, _ => escape_one(x), }; out.push_str(&first); for x in iter { let next = match x { b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' => unsafe { String::from_utf8_unchecked(vec![*x]) }, _ => escape_one(*x), }; out.push_str(&next); } out } #[cfg(test)] mod tests { use super::*; #[test] fn test_escape() { assert_eq!(escape("".to_string()), "_"); assert_eq!(escape("foo".to_string()), "foo"); assert_eq!(escape("foo@bar".to_string()), "foo_40bar"); assert_eq!(escape("foo_bar".to_string()), "foo_5fbar"); assert_eq!(escape("foo__@__bar".to_string()), "foo_5f_5f_40_5f_5fbar"); assert_eq!(escape("1foo".to_string()), "_31foo"); } } impl Connection { pub fn new(params: HashMap<&str, super::Variant>) -> Result { let err = Err(dbus::tree::MethodErr::no_arg()); let acct = match params.get("account") { Some(variant) => match variant.0.as_str() { Some(str) => str.to_string(), None => return err, }, None => return err, }; let id = escape(acct.to_owned()); let password = match params.get("password") { Some(variant) => match variant.0.as_str() { Some(str) => str.to_string(), None => return err, }, None => return err, }; let mut dbfile = directories::ProjectDirs::from("gs", "ur", "telepathy-padfoot") .ok_or_else(|| tree::MethodErr::no_arg()) .and_then(|p| Ok(p.data_local_dir().to_path_buf()))?; dbfile.push(&id); dbfile.push("db.sqlite3"); // FIXME: how to give it access to the connection (initialized later)? let id2 = id.clone(); let f = move |_c: &Context, e: Event| { match e { Event::Info(msg) => println!("Connection<{}>: INFO: {}", id2, msg), Event::Warning(msg) => println!("Connection<{}>: WARN : {}", id2, msg), Event::Error(msg) | Event::ErrorNetwork(msg) | Event::ErrorSelfNotInGroup(msg) => { println!("Connection<{}>: ERR : {}", id2, msg) } _ => println!("Connection<{}>: unhandled event received: {:?}", id2, e), }; }; let ctx = Context::new(Box::new(f), "telepathy-padfoot".to_string(), dbfile).map_err(|e| { println!("Connection<{}>: couldn't get delta context: {}", id, e); tree::MethodErr::no_arg() // FIXME: better error handling })?; ctx.set_config(Config::Addr, Some(&acct)) .map_err(|_e| tree::MethodErr::no_arg())?; ctx.set_config(Config::MailPw, Some(&password)) .map_err(|_e| tree::MethodErr::no_arg())?; ctx.set_config(Config::SentboxWatch, Some(&"Sent")) .map_err(|_e| tree::MethodErr::no_arg())?; if !ctx.is_configured() { ctx.configure(); }; Ok(Connection { id: id, ctx: Arc::new(RwLock::new(ctx)), }) } // This is run inside its own thread // // FIXME: running several +process+ loops sure is convenient, but it also // seems inefficient... pub fn run(self) { let bus = self.bus(); let path = self.path(); let ctx = self.ctx.clone(); let c_rc = std::rc::Rc::new(self); let f = tree::Factory::new_fn::<()>(); let iface = telepathy::connection_server(&f, (), move |_| c_rc.clone()); let mut tree = f.tree(()); tree = tree.add(f.object_path(path, ()).introspectable().add(iface)); tree = tree.add(f.object_path("/", ()).introspectable()); // Setup DBus connection let mut c = match LocalConnection::new_session() { Ok(c) => c, Err(e) => { println!("Failed to establish DBUS session for {}: {}", bus, e); return; // Leave early } }; tree.start_receive(&c); match c.request_name(bus.clone(), false, false, true) { Ok(RequestNameReply::Exists) => { println!("Another process is already registered on {}", bus); return; } Err(e) => { println!("Failed to register {}: {}", bus, e); return; } _ => println!("{} listening on {}", c.unique_name(), bus), // All other responses we can get are a success }; // Set up delta jobs last in case registering to DBUS fails // "Borrowed" from https://github.com/deltachat/deltachat-core-rust/blob/master/examples/simple.rs let running = Arc::new(RwLock::new(true)); let inbox_ctx = ctx.clone(); let r1 = running.clone(); let _t1 = thread::spawn(move || { while *r1.read().unwrap() { dc::job::perform_inbox_jobs(&inbox_ctx.read().unwrap()); if *r1.read().unwrap() { dc::job::perform_inbox_fetch(&inbox_ctx.read().unwrap()); if *r1.read().unwrap() { dc::job::perform_inbox_idle(&inbox_ctx.read().unwrap()); } } } }); let smtp_ctx = ctx.clone(); let r1 = running.clone(); let _t2 = thread::spawn(move || { while *r1.read().unwrap() { dc::job::perform_smtp_jobs(&smtp_ctx.read().unwrap()); if *r1.read().unwrap() { dc::job::perform_smtp_idle(&smtp_ctx.read().unwrap()); } } }); let mvbox_ctx = ctx.clone(); let r1 = running.clone(); let _t3 = thread::spawn(move || { while *r1.read().unwrap() { dc::job::perform_mvbox_fetch(&mvbox_ctx.read().unwrap()); if *r1.read().unwrap() { dc::job::perform_mvbox_idle(&mvbox_ctx.read().unwrap()); } } }); let sentbox_ctx = ctx.clone(); let r1 = running.clone(); let _t4 = thread::spawn(move || { while *r1.read().unwrap() { dc::job::perform_sentbox_fetch(&sentbox_ctx.read().unwrap()); if *r1.read().unwrap() { dc::job::perform_sentbox_idle(&sentbox_ctx.read().unwrap()); } } }); let r1 = running.clone(); while *r1.read().unwrap() { match c.process(Duration::from_millis(100)) { Err(e) => { println!("Error processing: {}", e); return; } _ => {} } // TODO: notice when the conn wants to exit } } pub fn bus(&self) -> String { CONN_BUS_NAME.to_owned() + "." + &self.id } pub fn path(&self) -> String { CONN_OBJECT_PATH.to_owned() + "/" + &self.id } } impl AsRef for std::rc::Rc { fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) { &**self } } impl telepathy::Connection for Connection { fn connect(&self) -> Result<(), tree::MethodErr> { Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn disconnect(&self) -> Result<(), tree::MethodErr> { println!("Connection<{}>::disconnect()", self.id); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn get_interfaces(&self) -> Result, tree::MethodErr> { println!("Connection<{}>::get_interfaces()", self.id); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn get_protocol(&self) -> Result { println!("Connection<{}>::get_protocol()", self.id); Ok(super::PROTO_NAME.to_string()) } fn get_self_handle(&self) -> Result { println!("Connection<{}>::get_self_handle()", self.id); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn get_status(&self) -> Result { println!("Connection<{}>::get_status()", self.id); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn hold_handles(&self, handle_type: u32, handles: Vec) -> Result<(), tree::MethodErr> { println!( "Connection<{}>::hold_handles({}, {:?})", self.id, handle_type, handles ); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn inspect_handles( &self, handle_type: u32, handles: Vec, ) -> Result, tree::MethodErr> { println!( "Connection<{}>::inspect_handles({}, {:?})", self.id, handle_type, handles ); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn list_channels( &self, ) -> Result, String, u32, u32)>, tree::MethodErr> { println!("Connection<{}>::list_channels()", self.id); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn release_handles(&self, handle_type: u32, handles: Vec) -> Result<(), tree::MethodErr> { println!( "Connection<{}>::release_handles({}, {:?})", self.id, handle_type, handles ); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn request_channel( &self, type_: &str, handle_type: u32, handle: u32, suppress_handler: bool, ) -> Result, tree::MethodErr> { println!( "Connection<{}>::request_channel({}, {}, {}, {})", self.id, type_, handle_type, handle, suppress_handler ); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn request_handles( &self, handle_type: u32, identifiers: Vec<&str>, ) -> Result, tree::MethodErr> { println!( "Connection<{}>::request_handles({}, {:?})", self.id, handle_type, identifiers ); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn add_client_interest(&self, tokens: Vec<&str>) -> Result<(), tree::MethodErr> { println!("Connection<{}>::add_client_interest({:?})", self.id, tokens); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn remove_client_interest(&self, tokens: Vec<&str>) -> Result<(), tree::MethodErr> { println!( "Connection<{}>::remove_client_interest({:?})", self.id, tokens ); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn interfaces(&self) -> Result, tree::MethodErr> { println!("Connection<{}>::interfaces()", self.id); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn self_handle(&self) -> Result { println!("Connection<{}>::self_handle()", self.id); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn self_id(&self) -> Result { println!("Connection<{}>::self_id()", self.id); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn status(&self) -> Result { println!("Connection<{}>::status()", self.id); Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? } fn has_immortal_handles(&self) -> Result { println!("Connection<{}>::has_immortal_handles()", self.id); Ok(true) } }