mod avatars; pub use self::avatars::*; #[allow(clippy::module_inception)] mod connection; pub use self::connection::*; mod contacts; pub use self::contacts::*; mod contact_list; pub use self::contact_list::*; mod escape; use self::escape::escape; mod requests; pub use self::requests::*; mod simple_presence; pub use self::simple_presence::*; use crate::telepathy; use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection}; use dbus::channel::Sender; use dbus::tree::MethodErr; use dc::config::Config; use dc::context::Context; use dc::Event; use deltachat as dc; use std::collections::{HashMap, HashSet, VecDeque}; use std::sync::{mpsc, Arc, Mutex, RwLock}; use std::time::Duration; pub const CONN_BUS_NAME: &str = "org.freedesktop.Telepathy.Connection.padfoot.delta"; pub const CONN_OBJECT_PATH: &str = "/org/freedesktop/Telepathy/Connection/padfoot/delta"; #[derive(Debug)] // A Deltachast connection uses email addresses as handles, and delta's Db IDs pub struct Connection { // Remove ourselves from this when done conns: Arc>>, ctx: Arc>, settings: ConnSettings, state: Arc>, // Used for sending out messages msgq: Arc>>, } #[derive(Debug)] pub struct ConnSettings { account: String, password: String, id: String, } impl ConnSettings { pub fn from_params(params: HashMap<&str, super::Variant>) -> Result { let err = Err(MethodErr::no_arg()); let account = 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(account.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, }; Ok(Self { account, password, id, }) } pub fn id(&self) -> String { self.id.to_owned() } 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 Connection { pub fn new( settings: ConnSettings, conns: Arc>>, ) -> Result { let mut dbfile = directories::ProjectDirs::from("gs", "ur", "telepathy-padfoot") .ok_or_else(MethodErr::no_arg) .and_then(|p| Ok(p.data_local_dir().to_path_buf()))?; dbfile.push(settings.id()); dbfile.push("db.sqlite3"); // FIXME: how to give it access to the connection (initialized later)? let msgq = Arc::new(Mutex::new(VecDeque::::new())); let id = settings.id(); // Use this if we need to send messages in response to DC events: // let msgq2 = msgq.clone(); let f = move |_c: &Context, e: Event| { match e { Event::Info(msg) => println!("Connection<{}>: INFO: {}", id, msg), Event::Warning(msg) => println!("Connection<{}>: WARN : {}", id, msg), Event::Error(msg) | Event::ErrorNetwork(msg) | Event::ErrorSelfNotInGroup(msg) => { println!("Connection<{}>: ERR : {}", id, msg) } Event::ConfigureProgress(progress) => { println!("Connection<{}>: Configuration progress: {}", id, progress) } Event::ImapConnected(msg) | Event::SmtpConnected(msg) => { println!("Connection<{}>: Network: {}", id, msg); } /* Unhandled messages: SmtpMessageSent(String), ImapMessageDeleted(String), ImapMessageMoved(String), ImapFolderEmptied(String), NewBlobFile(String), DeletedBlobFile(String), MsgsChanged IncomingMsg MsgDelivered MsgFailed MsgRead ChatModified(ChatId), ContactsChanged(Option), LocationChanged(Option), ImexProgress(usize), ImexFileWritten(PathBuf), SecurejoinInviterProgress SecurejoinJoinerProgress */ _ => println!("Connection<{}>: unhandled event received: {:?}", id, e), }; }; let ctx = Context::new(Box::new(f), "telepathy-padfoot".to_string(), dbfile).map_err(|e| { println!( "Connection<{}>::new(): couldn't get delta context: {}", settings.id(), e ); MethodErr::no_arg() // FIXME: better error handling })?; ctx.set_config(Config::Addr, Some(&settings.account)) .map_err(|_e| MethodErr::no_arg())?; ctx.set_config(Config::MailPw, Some(&settings.password)) .map_err(|_e| MethodErr::no_arg())?; ctx.set_config(Config::SentboxWatch, Some(&"Sent")) .map_err(|_e| MethodErr::no_arg())?; if !ctx.is_configured() { ctx.configure(); }; Ok(Connection { conns, settings, msgq, ctx: Arc::new(RwLock::new(ctx)), state: Arc::new(RwLock::new(ConnState::Initial)), }) } // This should be run inside its own thread. It will signal via the channel // once the main loop is ready // // FIXME: running several +process+ loops sure is convenient, but it also // seems inefficient... pub fn run(self, signal: mpsc::Sender>) { let id = self.id(); let bus = self.bus(); let path = self.path(); let conns = self.conns.clone(); let msgq = self.msgq.clone(); let state = self.state.clone(); let tree = self.build_tree(); // 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); signal.send(Some(MethodErr::no_arg())).unwrap(); return; } Err(e) => { println!("Failed to register {}: {}", bus, e); signal.send(Some(MethodErr::no_arg())).unwrap(); return; } _ => { // All other responses we can get are a success. We are now on // the message bus, so the caller can proceed println!("{} listening on {}", c.unique_name(), bus); signal.send(None).unwrap(); } }; // 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 while *state.read().unwrap() != ConnState::Disconnected { if let Err(e) = c.process(Duration::from_millis(100)) { println!("Error processing: {}", e); break; }; // Spend a bit of time sending any outgoing messages - signals, mostly while let Some(msg) = msgq.lock().unwrap().pop_front() { print!("Connection<{}>: Sending message...", id); match c.send(msg) { Err(e) => println!("error! {:?}", e), // FIXME: handle error better? _ => println!("OK!"), } } } // TODO: join on threads started in connect! let mut conns = conns.lock().expect("Mutex access"); conns.remove(&path); } pub fn id(&self) -> String { self.settings.id.to_string() } pub fn bus(&self) -> String { self.settings.bus() } pub fn path(&self) -> String { self.settings.path() } fn build_tree(self) -> dbus::tree::Tree { let path = self.path(); let c_rc = std::rc::Rc::new(self); let f = dbus::tree::Factory::new_fn::<()>(); let mut tree = f.tree(()); let c_rc1 = c_rc.clone(); let conn_iface = telepathy::connection_server(&f, (), move |_| c_rc1.clone()); let c_rc2 = c_rc.clone(); let avatars_iface = telepathy::connection_interface_avatars_server(&f, (), move |_| c_rc2.clone()); let c_rc3 = c_rc.clone(); let contacts_iface = telepathy::connection_interface_contacts_server(&f, (), move |_| c_rc3.clone()); let c_rc4 = c_rc.clone(); let contact_list_iface = telepathy::connection_interface_contact_list_server(&f, (), move |_| c_rc4.clone()); let c_rc5 = c_rc.clone(); let requests_iface = telepathy::connection_interface_requests_server(&f, (), move |_| c_rc5.clone()); let simple_presence_iface = telepathy::connection_interface_simple_presence_server(&f, (), move |_| c_rc.clone()); tree = tree.add( f.object_path(path, ()) .introspectable() .add(conn_iface) .add(avatars_iface) .add(contacts_iface) .add(contact_list_iface) .add(requests_iface) .add(simple_presence_iface), ); tree = tree.add(f.object_path("/", ()).introspectable()); tree } }