From 33d522779a8f90d8acc4d98280ea04a0aea50aac Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Sat, 16 May 2020 14:23:20 +0100 Subject: [PATCH] Split padfoot/connection.rs into multiple files --- README.md | 1 + src/padfoot/connection.rs | 732 ++-------------------- src/padfoot/connection/avatars.rs | 99 +++ src/padfoot/connection/connection.rs | 277 ++++++++ src/padfoot/connection/contact_list.rs | 103 +++ src/padfoot/connection/contacts.rs | 90 +++ src/padfoot/connection/requests.rs | 47 ++ src/padfoot/connection/simple_presence.rs | 68 ++ src/padfoot/connection_manager.rs | 8 +- src/padfoot/protocol.rs | 9 +- 10 files changed, 728 insertions(+), 706 deletions(-) create mode 100644 src/padfoot/connection/avatars.rs create mode 100644 src/padfoot/connection/connection.rs create mode 100644 src/padfoot/connection/contact_list.rs create mode 100644 src/padfoot/connection/contacts.rs create mode 100644 src/padfoot/connection/requests.rs create mode 100644 src/padfoot/connection/simple_presence.rs diff --git a/README.md b/README.md index 82779c4..a24b187 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Here's where we're at right now: - [x] Connect to deltachat-core-rust - [x] Set up an account via autoconfiguration - [x] Appear as online in Empathy +- [~] Disconnect! - [ ] Set up an account manually - [ ] Contacts handling - [ ] Text messages diff --git a/src/padfoot/connection.rs b/src/padfoot/connection.rs index f52fabd..a0837c0 100644 --- a/src/padfoot/connection.rs +++ b/src/padfoot/connection.rs @@ -1,9 +1,26 @@ +mod avatars; +pub use self::avatars::*; + +mod connection; +pub use self::connection::*; + +mod contacts; +pub use self::contacts::*; + +mod contact_list; +pub use self::contact_list::*; + +mod requests; +pub use self::requests::*; + +mod simple_presence; +pub use self::simple_presence::*; + use crate::telepathy; -use crate::telepathy::ConnectionInterfaceContacts; + use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection}; use dbus::channel::Sender; -use dbus::message::SignalArgs; -use dbus::{arg, tree}; +use dbus::tree::MethodErr; use dc::config::Config; use dc::context::Context; @@ -11,20 +28,11 @@ use dc::Event; use deltachat as dc; use std::collections::{HashMap, VecDeque}; -use std::convert::TryInto; use std::sync::{Arc, Mutex, 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, PartialEq, Eq)] -enum ConnState { - Initial, - Connected, - Disconnected, -} +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 @@ -38,26 +46,9 @@ pub struct Connection { msgq: Arc>>, } -pub fn connection_interfaces() -> Vec { - vec![ - "org.freedesktop.Telepathy.Connection".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.SimplePresence".to_string(), - ] -} - -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) { - &**self - } -} - impl Connection { - pub fn new(params: HashMap<&str, super::Variant>) -> Result { - let err = Err(dbus::tree::MethodErr::no_arg()); + pub fn new(params: HashMap<&str, super::Variant>) -> Result { + let err = Err(MethodErr::no_arg()); let acct = match params.get("account") { Some(variant) => match variant.0.as_str() { @@ -78,7 +69,7 @@ impl Connection { }; let mut dbfile = directories::ProjectDirs::from("gs", "ur", "telepathy-padfoot") - .ok_or_else(|| tree::MethodErr::no_arg()) + .ok_or(MethodErr::no_arg()) .and_then(|p| Ok(p.data_local_dir().to_path_buf()))?; dbfile.push(&id); @@ -130,22 +121,22 @@ impl Connection { 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 + MethodErr::no_arg() // FIXME: better error handling })?; ctx.set_config(Config::Addr, Some(&acct)) - .map_err(|_e| tree::MethodErr::no_arg())?; + .map_err(|_e| MethodErr::no_arg())?; ctx.set_config(Config::MailPw, Some(&password)) - .map_err(|_e| tree::MethodErr::no_arg())?; + .map_err(|_e| MethodErr::no_arg())?; ctx.set_config(Config::SentboxWatch, Some(&"Sent")) - .map_err(|_e| tree::MethodErr::no_arg())?; + .map_err(|_e| MethodErr::no_arg())?; if !ctx.is_configured() { ctx.configure(); }; Ok(Connection { - id: id, + id, ctx: Arc::new(RwLock::new(ctx)), state: Arc::new(RwLock::new(ConnState::Initial)), msgq: msgq.clone(), @@ -164,7 +155,7 @@ impl Connection { let id = self.id.clone(); let c_rc = std::rc::Rc::new(self); - let f = tree::Factory::new_fn::<()>(); + let f = dbus::tree::Factory::new_fn::<()>(); let mut tree = f.tree(()); let c_rc1 = c_rc.clone(); @@ -228,12 +219,10 @@ impl Connection { // 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 { - match c.process(Duration::from_millis(100)) { - Err(e) => { - println!("Error processing: {}", e); - return; - } - _ => {} + if let Err(e) = c.process(Duration::from_millis(100)) { + println!("Error processing: {}", e); + + return; } // Spend a bit of time sending any outgoing messages - signals, mostly @@ -264,655 +253,6 @@ impl Connection { } } -impl telepathy::Connection for Connection { - // In connect(), we start the threads that drive the deltachat context - fn connect(&self) -> Result<(), tree::MethodErr> { - println!("Connection<{}>::connect()", self.id); - - let inbox_ctx = self.ctx.clone(); - let state = self.state.clone(); - let id = self.id.clone(); - let _inbox_thread = thread::spawn(move || { - while *state.read().unwrap() != ConnState::Disconnected { - dc::job::perform_inbox_jobs(&inbox_ctx.read().unwrap()); - if *state.read().unwrap() != ConnState::Disconnected { - dc::job::perform_inbox_fetch(&inbox_ctx.read().unwrap()); - - if *state.read().unwrap() != ConnState::Disconnected { - dc::job::perform_inbox_idle(&inbox_ctx.read().unwrap()); - } - } - } - - println!("Connection<{}>::connect(): inbox thread exited", id); - }); - - let smtp_ctx = self.ctx.clone(); - let state = self.state.clone(); - let id = self.id.clone(); - let _smtp_thread = thread::spawn(move || { - while *state.read().unwrap() != ConnState::Disconnected { - dc::job::perform_smtp_jobs(&smtp_ctx.read().unwrap()); - if *state.read().unwrap() != ConnState::Disconnected { - dc::job::perform_smtp_idle(&smtp_ctx.read().unwrap()); - } - } - - println!("Connection<{}>::connect(): smtp thread exited", id); - }); - - let mvbox_ctx = self.ctx.clone(); - let state = self.state.clone(); - let id = self.id.clone(); - let _mvbox_thread = thread::spawn(move || { - while *state.read().unwrap() != ConnState::Disconnected { - dc::job::perform_mvbox_fetch(&mvbox_ctx.read().unwrap()); - if *state.read().unwrap() != ConnState::Disconnected { - dc::job::perform_mvbox_idle(&mvbox_ctx.read().unwrap()); - } - } - - println!("Connection<{}>::connect(): mvbox thread exited", id); - }); - - let sentbox_ctx = self.ctx.clone(); - let state = self.state.clone(); - let id = self.id.clone(); - let _sentbox_thread = thread::spawn(move || { - while *state.read().unwrap() != ConnState::Disconnected { - dc::job::perform_sentbox_fetch(&sentbox_ctx.read().unwrap()); - if *state.read().unwrap() != ConnState::Disconnected { - dc::job::perform_sentbox_idle(&sentbox_ctx.read().unwrap()); - } - } - - println!("Connection<{}>::connect(): sentbox thread exited", id); - }); - - // Just pretend to be connected all the time for now. Tracking IMAP+SMTP - // state is a pain - let state = self.state.clone(); - let mut w = state.write().unwrap(); - *w = ConnState::Connected; - - // Emit a StatusChanged signal for the benefit of others, but the caller - // learns from our RPC response - let sig = telepathy::ConnectionStatusChanged { - status: 0, // Connected - reason: 1, // Requested - }; - - let dbus_conn_path = dbus::strings::Path::new(self.path().to_string()) - .expect("Object path should meet DBUS requirements"); - - self.msgq - .clone() - .lock() - .unwrap() - .push_back(sig.to_emit_message(&dbus_conn_path)); - - Ok(()) - } - - fn disconnect(&self) -> Result<(), tree::MethodErr> { - println!("Connection<{}>::disconnect()", self.id); - - let state = self.state.clone(); - let mut w = state.write().unwrap(); - *w = ConnState::Disconnected; - - // FIXME: we need to signal to the CM that they should remove the - // connection from the active list - - Ok(()) - } - - fn interfaces(&self) -> Result, tree::MethodErr> { - println!("Connection<{}>::interfaces()", self.id); - - self.get_interfaces() - } - - fn get_interfaces(&self) -> Result, tree::MethodErr> { - println!("Connection<{}>::get_interfaces()", self.id); - - Ok(connection_interfaces()) - } - - fn get_protocol(&self) -> Result { - println!("Connection<{}>::get_protocol()", self.id); - - Ok(super::PROTO_NAME.to_string()) - } - - fn self_handle(&self) -> Result { - println!("Connection<{}>::self_handle()", self.id); - - self.get_self_handle() - } - - fn get_self_handle(&self) -> Result { - println!("Connection<{}>::get_self_handle()", self.id); - - Ok(dc::constants::DC_CONTACT_ID_SELF) - } - - fn status(&self) -> Result { - println!("Connection<{}>::status()", self.id); - - self.get_status() - } - - fn get_status(&self) -> Result { - match *self.state.clone().read().unwrap() { - ConnState::Initial | ConnState::Disconnected => Ok(2), - ConnState::Connected => Ok(0), - } - } - - fn hold_handles(&self, handle_type: u32, handles: Vec) -> Result<(), tree::MethodErr> { - println!( - "Connection<{}>::hold_handles({}, {:?})", - self.id, handle_type, handles - ); - - // Since HasImmortalHandles is true, this doesn't need to do anything - Ok(()) - } - - 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 - ); - - // Since HasImmortalHandles is true, we don't need to do anything - Ok(()) - } - - 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 self_id(&self) -> Result { - println!("Connection<{}>::self_id()", self.id); - - let contact = match dc::contact::Contact::get_by_id( - &self.ctx.read().unwrap(), - dc::constants::DC_CONTACT_ID_SELF, - ) { - Ok(c) => c, - Err(e) => { - println!(" err: {}", e); - return Err(tree::MethodErr::no_arg()); - } - }; - - Ok(contact.get_addr().to_string()) - } - - fn has_immortal_handles(&self) -> Result { - println!("Connection<{}>::has_immortal_handles()", self.id); - - Ok(true) - } -} - -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceAvatars + 'static) { - &**self - } -} - -// TODO: come back and do this properly, I'm just putting it in for consistency -impl telepathy::ConnectionInterfaceAvatars for Connection { - fn get_avatar_requirements( - &self, - ) -> Result<(Vec, u16, u16, u16, u16, u32), tree::MethodErr> { - println!("Connection<{}>::get_avatar_requirements()", self.id); - Ok((vec![], 0, 0, 0, 0, 0)) - } - - fn get_avatar_tokens(&self, contacts: Vec) -> Result, tree::MethodErr> { - println!("Connection<{}>::get_avatar_tokens({:?})", self.id, contacts); - Ok(vec![]) - } - - fn get_known_avatar_tokens( - &self, - contacts: Vec, - ) -> Result<::std::collections::HashMap, tree::MethodErr> { - println!( - "Connection<{}>::get_known_avatar_tokens({:?})", - self.id, contacts - ); - Ok(HashMap::::new()) - } - - fn request_avatar(&self, contact: u32) -> Result<(Vec, String), tree::MethodErr> { - println!("Connection<{}>::request_avatar({})", self.id, contact); - Ok((vec![], "".to_string())) - } - - fn request_avatars(&self, contacts: Vec) -> Result<(), tree::MethodErr> { - println!("Connection<{}>::request_avatar({:?})", self.id, contacts); - Ok(()) - } - - fn set_avatar(&self, _avatar: Vec, mimetype: &str) -> Result { - println!( - "Connection<{}>::set_avatar((data), {:?})", - self.id, mimetype - ); - Ok("".to_string()) - } - - fn clear_avatar(&self) -> Result<(), tree::MethodErr> { - println!("Connection<{}>::clear_avatar()", self.id); - Ok(()) - } - - fn supported_avatar_mimetypes(&self) -> Result, tree::MethodErr> { - println!("Connection<{}>::supported_avatar_mimetypes()", self.id); - Ok(vec![]) - } - - fn minimum_avatar_height(&self) -> Result { - println!("Connection<{}>::minimum_avatar_height()", self.id); - Ok(0) - } - - fn minimum_avatar_width(&self) -> Result { - println!("Connection<{}>::minimum_avatar_width()", self.id); - Ok(0) - } - - fn recommended_avatar_height(&self) -> Result { - println!("Connection<{}>::recommended_avatar_height()", self.id); - Ok(0) - } - - fn recommended_avatar_width(&self) -> Result { - println!("Connection<{}>::recommended_avatar_width()", self.id); - Ok(0) - } - - fn maximum_avatar_height(&self) -> Result { - println!("Connection<{}>::maximum_avatar_height()", self.id); - Ok(0) - } - - fn maximum_avatar_width(&self) -> Result { - println!("Connection<{}>::maximum_avatar_width()", self.id); - Ok(0) - } - - fn maximum_avatar_bytes(&self) -> Result { - println!("Connection<{}>::maximum_avatar_bytes()", self.id); - Ok(0) - } -} - -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContacts + 'static) { - &**self - } -} - -impl telepathy::ConnectionInterfaceContacts for Connection { - fn get_contact_attributes( - &self, - handles: Vec, - interfaces: Vec<&str>, - hold: bool, - ) -> Result>, tree::MethodErr> { - println!( - "Connection<{}>::get_contact_attributes({:?}, {:?}, {})", - self.id, handles, interfaces, hold - ); - - let mut out = HashMap::>::new(); - for id in handles.iter() { - // FIXME: work out how to use get_all - let contact = match dc::contact::Contact::get_by_id(&self.ctx.read().unwrap(), *id) { - Ok(c) => c, - Err(_e) => continue, // Invalid IDs are silently ignored - }; - - let mut props = HashMap::::new(); - // This is mandatory - props.insert( - "org.freedesktop.Telepathy.Connection/contact-id".to_string(), - arg::Variant(Box::new(contact.get_addr().to_string())), - ); - - // The empty string means "no avatar" - props.insert( - "org.freedesktop.Telepathy.Connection.Interface.Avatars/token".to_string(), - arg::Variant(Box::new("".to_string())), - ); - - // TODO: we need to publish DBUS services on these endpoints - props.insert( - "org.freedesktop.Telepathy.Connection.Interface.ContactList/publish".to_string(), - arg::Variant(Box::new(4)), - ); - - props.insert( - "org.freedesktop.Telepathy.Connection.Interface.ContactList/subscribe".to_string(), - arg::Variant(Box::new(4)), - ); - - out.insert(*id, props); - } - - Ok(out) - } - - fn get_contact_by_id( - &self, - identifier: &str, - interfaces: Vec<&str>, - ) -> Result< - ( - u32, - ::std::collections::HashMap>>, - ), - tree::MethodErr, - > { - println!( - "Connection<{}>::get_contact_by_id({}, {:?})", - self.id, identifier, interfaces - ); - Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? - } - - fn contact_attribute_interfaces(&self) -> Result, tree::MethodErr> { - println!("Connection<{}>::contact_attribute_interfaces()", self.id); - - Ok(vec![ - "org.freedesktop.Telepathy.Connection".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(), - ]) - } -} - -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContactList + 'static) { - &**self - } -} - -// FIXME: come back and do this properly later -impl telepathy::ConnectionInterfaceContactList for Connection { - fn get_contact_list_attributes( - &self, - interfaces: Vec<&str>, - hold: bool, - ) -> Result>, tree::MethodErr> { - println!( - "Connection<{}>::get_contact_list_attributes({:?}, {})", - self.id, interfaces, hold - ); - - let ids = match dc::contact::Contact::get_all( - &self.ctx.read().unwrap(), - dc::constants::DC_GCL_ADD_SELF.try_into().unwrap(), - None::, - ) { - Ok(c) => c, - Err(e) => { - println!(" err: {}", e); - return Err(tree::MethodErr::no_arg()); - } - }; - - self.get_contact_attributes(ids, vec![], true) - } - - fn request_subscription( - &self, - contacts: Vec, - message: &str, - ) -> Result<(), tree::MethodErr> { - println!( - "Connection<{}>::request_subscription({:?}, {})", - self.id, contacts, message - ); - Ok(()) - } - - fn authorize_publication(&self, contacts: Vec) -> Result<(), tree::MethodErr> { - println!( - "Connection<{}>::authorize_publication({:?})", - self.id, contacts - ); - Ok(()) - } - fn remove_contacts(&self, contacts: Vec) -> Result<(), tree::MethodErr> { - println!("Connection<{}>::remove_contacts({:?})", self.id, contacts); - Ok(()) - } - fn unsubscribe(&self, contacts: Vec) -> Result<(), tree::MethodErr> { - println!("Connection<{}>::unsubscribe({:?})", self.id, contacts); - Ok(()) - } - - fn unpublish(&self, contacts: Vec) -> Result<(), tree::MethodErr> { - println!("Connection<{}>::unpublish({:?})", self.id, contacts); - Ok(()) - } - - fn download(&self) -> Result<(), tree::MethodErr> { - println!("Connection<{}>::download()", self.id); - Ok(()) - } - - fn contact_list_state(&self) -> Result { - println!("Connection<{}>::contact_list_state()", self.id); - Ok(3) // Success - } - - fn contact_list_persists(&self) -> Result { - println!("Connection<{}>::contact_list_persists()", self.id); - Ok(true) - } - - fn can_change_contact_list(&self) -> Result { - println!("Connection<{}>::can_change_contact_list()", self.id); - Ok(false) - } - fn request_uses_message(&self) -> Result { - println!("Connection<{}>::request_uses_message()", self.id); - Ok(false) - } - - fn download_at_connection(&self) -> Result { - println!("Connection<{}>::download_at_connection()", self.id); - Ok(false) - } -} - -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceRequests + 'static) { - &**self - } -} - -impl telepathy::ConnectionInterfaceRequests for Connection { - fn create_channel( - &self, - request: ::std::collections::HashMap<&str, arg::Variant>>, - ) -> Result< - ( - dbus::Path<'static>, - ::std::collections::HashMap>>, - ), - tree::MethodErr, - > { - println!("Connection<{}>::create_channel({:?})", self.id, request); - Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? - } - - fn ensure_channel( - &self, - request: ::std::collections::HashMap<&str, arg::Variant>>, - ) -> Result< - ( - bool, - dbus::Path<'static>, - ::std::collections::HashMap>>, - ), - tree::MethodErr, - > { - println!("Connection<{}>::ensure_channel({:?})", self.id, request); - Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? - } - - fn channels( - &self, - ) -> Result< - Vec<( - dbus::Path<'static>, - ::std::collections::HashMap>>, - )>, - tree::MethodErr, - > { - println!("Connection<{}>::channels()", self.id); - Ok(vec![]) - } - - fn requestable_channel_classes( - &self, - ) -> Result< - Vec<( - ::std::collections::HashMap>>, - Vec, - )>, - tree::MethodErr, - > { - println!("Connection<{}>::requestable_channel_classes()", self.id); - Ok(super::requestables()) - } -} - -pub type SimplePresenceSpec = ( - u32, // connection presence type - String, // status - String, // status message -); - -pub type SimpleStatusSpec = ( - u32, // connection presence type - bool, // may set on self? - bool, // can have message? -); - -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceSimplePresence + 'static) { - &**self - } -} - -impl telepathy::ConnectionInterfaceSimplePresence for Connection { - fn set_presence(&self, status: &str, status_message: &str) -> Result<(), tree::MethodErr> { - println!( - "Connection<{}>::set_presence({}, {:?})", - self.id, status, status_message - ); - - // FIXME: emit a presence changed signal - - Ok(()) - } - - fn get_presences( - &self, - contacts: Vec, - ) -> Result, tree::MethodErr> { - println!("Connection<{}>::get_presences({:?})", self.id, contacts); - - let mut out = HashMap::::new(); - - for id in contacts { - out.insert(id, (2, "available".to_string(), "".to_string())); // Available - } - - Ok(out) - } - - fn statuses(&self) -> Result, tree::MethodErr> { - println!("Connection<{}>::statuses()", self.id); - - let mut out = HashMap::::new(); - - out.insert("available".to_string(), (2, true, false)); - out.insert("offline".to_string(), (1, true, false)); - - Ok(out) - } - - fn maximum_status_message_length(&self) -> Result { - println!("Connection<{}>::maximum_status_message_length()", self.id); - Ok(0) - } -} - fn escape_one(b: u8) -> String { format!("_{:0<2x}", b) } @@ -920,7 +260,7 @@ fn escape_one(b: u8) -> String { // Some non-empty sequence of ASCII letters, digits and underscores fn escape(s: String) -> String { // Special-case the empty string - if s.len() == 0 { + if s.is_empty() { return "_".to_string(); } diff --git a/src/padfoot/connection/avatars.rs b/src/padfoot/connection/avatars.rs new file mode 100644 index 0000000..6b2fadc --- /dev/null +++ b/src/padfoot/connection/avatars.rs @@ -0,0 +1,99 @@ +use crate::telepathy; + +use dbus::tree::MethodErr; +use std::collections::HashMap; + +use super::Connection; + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceAvatars + 'static) { + &**self + } +} + +// TODO: come back and do this properly, I'm just putting it in for consistency +impl telepathy::ConnectionInterfaceAvatars for Connection { + fn get_avatar_requirements(&self) -> Result<(Vec, u16, u16, u16, u16, u32), MethodErr> { + println!("Connection<{}>::get_avatar_requirements()", self.id); + Ok((vec![], 0, 0, 0, 0, 0)) + } + + fn get_avatar_tokens(&self, contacts: Vec) -> Result, MethodErr> { + println!("Connection<{}>::get_avatar_tokens({:?})", self.id, contacts); + Ok(vec![]) + } + + fn get_known_avatar_tokens( + &self, + contacts: Vec, + ) -> Result<::std::collections::HashMap, MethodErr> { + println!( + "Connection<{}>::get_known_avatar_tokens({:?})", + self.id, contacts + ); + Ok(HashMap::::new()) + } + + fn request_avatar(&self, contact: u32) -> Result<(Vec, String), MethodErr> { + println!("Connection<{}>::request_avatar({})", self.id, contact); + Ok((vec![], "".to_string())) + } + + fn request_avatars(&self, contacts: Vec) -> Result<(), MethodErr> { + println!("Connection<{}>::request_avatar({:?})", self.id, contacts); + Ok(()) + } + + fn set_avatar(&self, _avatar: Vec, mimetype: &str) -> Result { + println!( + "Connection<{}>::set_avatar((data), {:?})", + self.id, mimetype + ); + Ok("".to_string()) + } + + fn clear_avatar(&self) -> Result<(), MethodErr> { + println!("Connection<{}>::clear_avatar()", self.id); + Ok(()) + } + + fn supported_avatar_mimetypes(&self) -> Result, MethodErr> { + println!("Connection<{}>::supported_avatar_mimetypes()", self.id); + Ok(vec![]) + } + + fn minimum_avatar_height(&self) -> Result { + println!("Connection<{}>::minimum_avatar_height()", self.id); + Ok(0) + } + + fn minimum_avatar_width(&self) -> Result { + println!("Connection<{}>::minimum_avatar_width()", self.id); + Ok(0) + } + + fn recommended_avatar_height(&self) -> Result { + println!("Connection<{}>::recommended_avatar_height()", self.id); + Ok(0) + } + + fn recommended_avatar_width(&self) -> Result { + println!("Connection<{}>::recommended_avatar_width()", self.id); + Ok(0) + } + + fn maximum_avatar_height(&self) -> Result { + println!("Connection<{}>::maximum_avatar_height()", self.id); + Ok(0) + } + + fn maximum_avatar_width(&self) -> Result { + println!("Connection<{}>::maximum_avatar_width()", self.id); + Ok(0) + } + + fn maximum_avatar_bytes(&self) -> Result { + println!("Connection<{}>::maximum_avatar_bytes()", self.id); + Ok(0) + } +} diff --git a/src/padfoot/connection/connection.rs b/src/padfoot/connection/connection.rs new file mode 100644 index 0000000..4b87b63 --- /dev/null +++ b/src/padfoot/connection/connection.rs @@ -0,0 +1,277 @@ +use crate::telepathy; + +use dbus::message::SignalArgs; +use dbus::tree::MethodErr; +use deltachat as dc; +use std::thread; + +use super::Connection; + +#[derive(Debug, PartialEq, Eq)] +pub enum ConnState { + Initial, + Connected, + Disconnected, +} + +pub fn connection_interfaces() -> Vec { + vec![ + "org.freedesktop.Telepathy.Connection".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.SimplePresence".to_string(), + ] +} + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) { + &**self + } +} + +impl telepathy::Connection for Connection { + // In connect(), we start the threads that drive the deltachat context + fn connect(&self) -> Result<(), MethodErr> { + println!("Connection<{}>::connect()", self.id); + + let inbox_ctx = self.ctx.clone(); + let state = self.state.clone(); + let id = self.id.clone(); + let _inbox_thread = thread::spawn(move || { + while *state.read().unwrap() != ConnState::Disconnected { + dc::job::perform_inbox_jobs(&inbox_ctx.read().unwrap()); + if *state.read().unwrap() != ConnState::Disconnected { + dc::job::perform_inbox_fetch(&inbox_ctx.read().unwrap()); + + if *state.read().unwrap() != ConnState::Disconnected { + dc::job::perform_inbox_idle(&inbox_ctx.read().unwrap()); + } + } + } + + println!("Connection<{}>::connect(): inbox thread exited", id); + }); + + let smtp_ctx = self.ctx.clone(); + let state = self.state.clone(); + let id = self.id.clone(); + let _smtp_thread = thread::spawn(move || { + while *state.read().unwrap() != ConnState::Disconnected { + dc::job::perform_smtp_jobs(&smtp_ctx.read().unwrap()); + if *state.read().unwrap() != ConnState::Disconnected { + dc::job::perform_smtp_idle(&smtp_ctx.read().unwrap()); + } + } + + println!("Connection<{}>::connect(): smtp thread exited", id); + }); + + let mvbox_ctx = self.ctx.clone(); + let state = self.state.clone(); + let id = self.id.clone(); + let _mvbox_thread = thread::spawn(move || { + while *state.read().unwrap() != ConnState::Disconnected { + dc::job::perform_mvbox_fetch(&mvbox_ctx.read().unwrap()); + if *state.read().unwrap() != ConnState::Disconnected { + dc::job::perform_mvbox_idle(&mvbox_ctx.read().unwrap()); + } + } + + println!("Connection<{}>::connect(): mvbox thread exited", id); + }); + + let sentbox_ctx = self.ctx.clone(); + let state = self.state.clone(); + let id = self.id.clone(); + let _sentbox_thread = thread::spawn(move || { + while *state.read().unwrap() != ConnState::Disconnected { + dc::job::perform_sentbox_fetch(&sentbox_ctx.read().unwrap()); + if *state.read().unwrap() != ConnState::Disconnected { + dc::job::perform_sentbox_idle(&sentbox_ctx.read().unwrap()); + } + } + + println!("Connection<{}>::connect(): sentbox thread exited", id); + }); + + // Just pretend to be connected all the time for now. Tracking IMAP+SMTP + // state is a pain + let state = self.state.clone(); + let mut w = state.write().unwrap(); + *w = ConnState::Connected; + + // Emit a StatusChanged signal for the benefit of others, but the caller + // learns from our RPC response + let sig = telepathy::ConnectionStatusChanged { + status: 0, // Connected + reason: 1, // Requested + }; + + let dbus_conn_path = dbus::strings::Path::new(self.path()) + .expect("Object path should meet DBUS requirements"); + + self.msgq + .lock() + .unwrap() + .push_back(sig.to_emit_message(&dbus_conn_path)); + + Ok(()) + } + + fn disconnect(&self) -> Result<(), MethodErr> { + println!("Connection<{}>::disconnect()", self.id); + + let state = self.state.clone(); + let mut w = state.write().unwrap(); + *w = ConnState::Disconnected; + + // FIXME: we need to signal to the CM that they should remove the + // connection from the active list + + Ok(()) + } + + fn interfaces(&self) -> Result, MethodErr> { + println!("Connection<{}>::interfaces()", self.id); + + self.get_interfaces() + } + + fn get_interfaces(&self) -> Result, MethodErr> { + println!("Connection<{}>::get_interfaces()", self.id); + + Ok(connection_interfaces()) + } + + fn get_protocol(&self) -> Result { + println!("Connection<{}>::get_protocol()", self.id); + + Ok(crate::padfoot::PROTO_NAME.to_string()) + } + + fn self_handle(&self) -> Result { + println!("Connection<{}>::self_handle()", self.id); + + self.get_self_handle() + } + + fn get_self_handle(&self) -> Result { + println!("Connection<{}>::get_self_handle()", self.id); + + Ok(dc::constants::DC_CONTACT_ID_SELF) + } + + fn status(&self) -> Result { + println!("Connection<{}>::status()", self.id); + + self.get_status() + } + + fn get_status(&self) -> Result { + match *self.state.clone().read().unwrap() { + ConnState::Initial | ConnState::Disconnected => Ok(2), + ConnState::Connected => Ok(0), + } + } + + fn hold_handles(&self, handle_type: u32, handles: Vec) -> Result<(), MethodErr> { + println!( + "Connection<{}>::hold_handles({}, {:?})", + self.id, handle_type, handles + ); + + // Since HasImmortalHandles is true, this doesn't need to do anything + Ok(()) + } + + fn inspect_handles( + &self, + handle_type: u32, + handles: Vec, + ) -> Result, MethodErr> { + println!( + "Connection<{}>::inspect_handles({}, {:?})", + self.id, handle_type, handles + ); + Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + } + + fn list_channels(&self) -> Result, String, u32, u32)>, MethodErr> { + println!("Connection<{}>::list_channels()", self.id); + Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + } + + fn release_handles(&self, handle_type: u32, handles: Vec) -> Result<(), MethodErr> { + println!( + "Connection<{}>::release_handles({}, {:?})", + self.id, handle_type, handles + ); + + // Since HasImmortalHandles is true, we don't need to do anything + Ok(()) + } + + fn request_channel( + &self, + type_: &str, + handle_type: u32, + handle: u32, + suppress_handler: bool, + ) -> Result, MethodErr> { + println!( + "Connection<{}>::request_channel({}, {}, {}, {})", + self.id, type_, handle_type, handle, suppress_handler + ); + Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + } + + fn request_handles( + &self, + handle_type: u32, + identifiers: Vec<&str>, + ) -> Result, MethodErr> { + println!( + "Connection<{}>::request_handles({}, {:?})", + self.id, handle_type, identifiers + ); + Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + } + + fn add_client_interest(&self, tokens: Vec<&str>) -> Result<(), MethodErr> { + println!("Connection<{}>::add_client_interest({:?})", self.id, tokens); + Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + } + + fn remove_client_interest(&self, tokens: Vec<&str>) -> Result<(), MethodErr> { + println!( + "Connection<{}>::remove_client_interest({:?})", + self.id, tokens + ); + Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + } + + fn self_id(&self) -> Result { + println!("Connection<{}>::self_id()", self.id); + + let contact = match dc::contact::Contact::get_by_id( + &self.ctx.read().unwrap(), + dc::constants::DC_CONTACT_ID_SELF, + ) { + Ok(c) => c, + Err(e) => { + println!(" err: {}", e); + return Err(MethodErr::no_arg()); + } + }; + + Ok(contact.get_addr().to_string()) + } + + fn has_immortal_handles(&self) -> Result { + println!("Connection<{}>::has_immortal_handles()", self.id); + + Ok(true) + } +} diff --git a/src/padfoot/connection/contact_list.rs b/src/padfoot/connection/contact_list.rs new file mode 100644 index 0000000..c651dad --- /dev/null +++ b/src/padfoot/connection/contact_list.rs @@ -0,0 +1,103 @@ +use crate::telepathy; + +use dbus::arg::{RefArg, Variant}; +use dbus::tree::MethodErr; +use deltachat::constants::DC_GCL_ADD_SELF; +use deltachat::contact::Contact; +use std::collections::HashMap; +use std::convert::TryInto; +use telepathy::ConnectionInterfaceContacts; // for get_contact_attributes + +use super::Connection; + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContactList + 'static) { + &**self + } +} + +// TODO: extract a utility module for this? +type VarArg = Variant>; + +// FIXME: come back and do this properly later +impl telepathy::ConnectionInterfaceContactList for Connection { + fn get_contact_list_attributes( + &self, + interfaces: Vec<&str>, + hold: bool, + ) -> Result>, MethodErr> { + println!( + "Connection<{}>::get_contact_list_attributes({:?}, {})", + self.id, interfaces, hold + ); + + let ctx = &self.ctx.read().unwrap(); + let ids = match Contact::get_all(ctx, DC_GCL_ADD_SELF.try_into().unwrap(), None::) { + Ok(c) => c, + Err(e) => { + println!(" err: {}", e); + return Err(MethodErr::no_arg()); + } + }; + + self.get_contact_attributes(ids, vec![], true) + } + + fn request_subscription(&self, contacts: Vec, message: &str) -> Result<(), MethodErr> { + println!( + "Connection<{}>::request_subscription({:?}, {})", + self.id, contacts, message + ); + Ok(()) + } + + fn authorize_publication(&self, contacts: Vec) -> Result<(), MethodErr> { + println!( + "Connection<{}>::authorize_publication({:?})", + self.id, contacts + ); + Ok(()) + } + fn remove_contacts(&self, contacts: Vec) -> Result<(), MethodErr> { + println!("Connection<{}>::remove_contacts({:?})", self.id, contacts); + Ok(()) + } + fn unsubscribe(&self, contacts: Vec) -> Result<(), MethodErr> { + println!("Connection<{}>::unsubscribe({:?})", self.id, contacts); + Ok(()) + } + + fn unpublish(&self, contacts: Vec) -> Result<(), MethodErr> { + println!("Connection<{}>::unpublish({:?})", self.id, contacts); + Ok(()) + } + + fn download(&self) -> Result<(), MethodErr> { + println!("Connection<{}>::download()", self.id); + Ok(()) + } + + fn contact_list_state(&self) -> Result { + println!("Connection<{}>::contact_list_state()", self.id); + Ok(3) // Success + } + + fn contact_list_persists(&self) -> Result { + println!("Connection<{}>::contact_list_persists()", self.id); + Ok(true) + } + + fn can_change_contact_list(&self) -> Result { + println!("Connection<{}>::can_change_contact_list()", self.id); + Ok(false) + } + fn request_uses_message(&self) -> Result { + println!("Connection<{}>::request_uses_message()", self.id); + Ok(false) + } + + fn download_at_connection(&self) -> Result { + println!("Connection<{}>::download_at_connection()", self.id); + Ok(false) + } +} diff --git a/src/padfoot/connection/contacts.rs b/src/padfoot/connection/contacts.rs new file mode 100644 index 0000000..46f3950 --- /dev/null +++ b/src/padfoot/connection/contacts.rs @@ -0,0 +1,90 @@ +use crate::telepathy; + +use dbus::arg::{RefArg, Variant}; +use dbus::tree::MethodErr; +use deltachat::contact::Contact; +use std::collections::HashMap; + +use super::Connection; + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContacts + 'static) { + &**self + } +} + +// TODO: extract a utility module for this? +type VarArgs = Variant>; + +impl telepathy::ConnectionInterfaceContacts for Connection { + fn get_contact_attributes( + &self, + handles: Vec, + interfaces: Vec<&str>, + hold: bool, + ) -> Result>, MethodErr> { + println!( + "Connection<{}>::get_contact_attributes({:?}, {:?}, {})", + self.id, handles, interfaces, hold + ); + + let mut out = HashMap::>::new(); + for id in handles.iter() { + // FIXME: work out how to use get_all + let contact = match Contact::get_by_id(&self.ctx.read().unwrap(), *id) { + Ok(c) => c, + Err(_e) => continue, // Invalid IDs are silently ignored + }; + + let mut props = HashMap::::new(); + // This is mandatory + props.insert( + "org.freedesktop.Telepathy.Connection/contact-id".to_string(), + Variant(Box::new(contact.get_addr().to_string())), + ); + + // The empty string means "no avatar" + props.insert( + "org.freedesktop.Telepathy.Connection.Interface.Avatars/token".to_string(), + Variant(Box::new("".to_string())), + ); + + // TODO: we need to publish DBUS services on these endpoints + props.insert( + "org.freedesktop.Telepathy.Connection.Interface.ContactList/publish".to_string(), + Variant(Box::new(4)), + ); + + props.insert( + "org.freedesktop.Telepathy.Connection.Interface.ContactList/subscribe".to_string(), + Variant(Box::new(4)), + ); + + out.insert(*id, props); + } + + Ok(out) + } + + fn get_contact_by_id( + &self, + identifier: &str, + interfaces: Vec<&str>, + ) -> Result<(u32, HashMap), MethodErr> { + println!( + "Connection<{}>::get_contact_by_id({}, {:?})", + self.id, identifier, interfaces + ); + Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + } + + fn contact_attribute_interfaces(&self) -> Result, MethodErr> { + println!("Connection<{}>::contact_attribute_interfaces()", self.id); + + Ok(vec![ + "org.freedesktop.Telepathy.Connection".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(), + ]) + } +} diff --git a/src/padfoot/connection/requests.rs b/src/padfoot/connection/requests.rs new file mode 100644 index 0000000..b7298db --- /dev/null +++ b/src/padfoot/connection/requests.rs @@ -0,0 +1,47 @@ +use crate::telepathy; + +use dbus::arg::{RefArg, Variant}; +use dbus::tree::MethodErr; +use std::collections::HashMap; + +use super::Connection; + +// TODO: extract a utility module for this? +type VarArg = Variant>; + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceRequests + 'static) { + &**self + } +} + +impl telepathy::ConnectionInterfaceRequests for Connection { + fn create_channel( + &self, + request: HashMap<&str, VarArg>, + ) -> Result<(dbus::Path<'static>, HashMap), MethodErr> { + println!("Connection<{}>::create_channel({:?})", self.id, request); + + Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + } + + fn ensure_channel( + &self, + request: HashMap<&str, VarArg>, + ) -> Result<(bool, dbus::Path<'static>, HashMap), MethodErr> { + println!("Connection<{}>::ensure_channel({:?})", self.id, request); + Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + } + + fn channels(&self) -> Result, HashMap)>, MethodErr> { + println!("Connection<{}>::channels()", self.id); + Ok(vec![]) + } + + fn requestable_channel_classes( + &self, + ) -> Result, Vec)>, MethodErr> { + println!("Connection<{}>::requestable_channel_classes()", self.id); + Ok(crate::padfoot::requestables()) + } +} diff --git a/src/padfoot/connection/simple_presence.rs b/src/padfoot/connection/simple_presence.rs new file mode 100644 index 0000000..da00c11 --- /dev/null +++ b/src/padfoot/connection/simple_presence.rs @@ -0,0 +1,68 @@ +use crate::telepathy; + +use dbus::tree::MethodErr; +use std::collections::HashMap; + +use super::Connection; + +pub type SimplePresenceSpec = ( + u32, // connection presence type + String, // status + String, // status message +); + +pub type SimpleStatusSpec = ( + u32, // connection presence type + bool, // may set on self? + bool, // can have message? +); + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceSimplePresence + 'static) { + &**self + } +} + +impl telepathy::ConnectionInterfaceSimplePresence for Connection { + fn set_presence(&self, status: &str, status_message: &str) -> Result<(), MethodErr> { + println!( + "Connection<{}>::set_presence({}, {:?})", + self.id, status, status_message + ); + + // FIXME: emit a presence changed signal + + Ok(()) + } + + fn get_presences( + &self, + contacts: Vec, + ) -> Result, MethodErr> { + println!("Connection<{}>::get_presences({:?})", self.id, contacts); + + let mut out = HashMap::::new(); + + for id in contacts { + out.insert(id, (2, "available".to_string(), "".to_string())); // Available + } + + Ok(out) + } + + fn statuses(&self) -> Result, MethodErr> { + println!("Connection<{}>::statuses()", self.id); + + let mut out = HashMap::::new(); + + out.insert("available".to_string(), (2, true, false)); + out.insert("offline".to_string(), (1, true, false)); + + Ok(out) + } + + fn maximum_status_message_length(&self) -> Result { + println!("Connection<{}>::maximum_status_message_length()", self.id); + Ok(0) + } +} diff --git a/src/padfoot/connection_manager.rs b/src/padfoot/connection_manager.rs index 333eb6b..8dab31c 100644 --- a/src/padfoot/connection_manager.rs +++ b/src/padfoot/connection_manager.rs @@ -4,9 +4,9 @@ use dbus::{arg, message::SignalArgs, tree}; use std::collections::{HashMap, HashSet}; use std::sync::mpsc; -pub const CM_BUS_NAME: &'static str = "org.freedesktop.Telepathy.ConnectionManager.padfoot"; -pub const CM_CONN_BUS_NAME: &'static str = "org.freedesktop.Telepathy.Connection.padfoot"; -pub const CM_OBJECT_PATH: &'static str = "/org/freedesktop/Telepathy/ConnectionManager/padfoot"; +pub const CM_BUS_NAME: &str = "org.freedesktop.Telepathy.ConnectionManager.padfoot"; +pub const CM_CONN_BUS_NAME: &str = "org.freedesktop.Telepathy.Connection.padfoot"; +pub const CM_OBJECT_PATH: &str = "/org/freedesktop/Telepathy/ConnectionManager/padfoot"; #[derive(Debug)] pub struct ConnectionManager { @@ -50,7 +50,7 @@ impl ConnectionManager { return Ok((bus, dbus_conn_path)); } - conns.insert(path.clone()); + conns.insert(path); // FIXME: this thread races with the responses we send. It's possible // the remotes will try to use the new names before they exist on the diff --git a/src/padfoot/protocol.rs b/src/padfoot/protocol.rs index e0ca2cd..e370885 100644 --- a/src/padfoot/protocol.rs +++ b/src/padfoot/protocol.rs @@ -2,12 +2,9 @@ use crate::telepathy; use dbus::{arg, tree}; use std::collections::HashMap; -pub const PROTO_OBJECT_PATH: &'static str = - "/org/freedesktop/Telepathy/ConnectionManager/padfoot/delta"; -pub const PROTO_BUS_NAME: &'static str = - "org.freedesktop.Telepathy.ConnectionManager.padfoot.delta"; - -pub const PROTO_NAME: &'static str = "delta"; +pub const PROTO_OBJECT_PATH: &str = "/org/freedesktop/Telepathy/ConnectionManager/padfoot/delta"; +pub const PROTO_BUS_NAME: &str = "org.freedesktop.Telepathy.ConnectionManager.padfoot.delta"; +pub const PROTO_NAME: &str = "delta"; #[derive(Debug)] pub struct Protocol {}