diff --git a/src/padfoot/connection.rs b/src/padfoot/connection.rs index cbff5b2..f52fabd 100644 --- a/src/padfoot/connection.rs +++ b/src/padfoot/connection.rs @@ -1,4 +1,5 @@ use crate::telepathy; +use crate::telepathy::ConnectionInterfaceContacts; use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection}; use dbus::channel::Sender; use dbus::message::SignalArgs; @@ -10,6 +11,7 @@ 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; @@ -25,6 +27,7 @@ enum ConnState { } #[derive(Debug)] +// A Deltachast connection uses email addresses as handles, and delta's Db IDs pub struct Connection { id: String, ctx: Arc>, @@ -35,6 +38,23 @@ 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()); @@ -151,19 +171,34 @@ impl Connection { let conn_iface = telepathy::connection_server(&f, (), move |_| c_rc1.clone()); let c_rc2 = c_rc.clone(); - let contacts_iface = - telepathy::connection_interface_contacts_server(&f, (), move |_| c_rc2.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_rc3.clone()); + telepathy::connection_interface_requests_server(&f, (), move |_| c_rc5.clone()); + + let c_rc6 = c_rc.clone(); + let simple_presence_iface = + telepathy::connection_interface_simple_presence_server(&f, (), move |_| c_rc6.clone()); tree = tree.add( f.object_path(path.clone(), ()) .introspectable() .add(conn_iface) + .add(avatars_iface) .add(contacts_iface) - .add(requests_iface), + .add(contact_list_iface) + .add(requests_iface) + .add(simple_presence_iface), ); tree = tree.add(f.object_path("/", ()).introspectable()); @@ -229,24 +264,6 @@ impl Connection { } } -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) { - &**self - } -} - -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContacts + 'static) { - &**self - } -} - -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceRequests + 'static) { - &**self - } -} - impl telepathy::Connection for Connection { // In connect(), we start the threads that drive the deltachat context fn connect(&self) -> Result<(), tree::MethodErr> { @@ -254,6 +271,7 @@ impl telepathy::Connection for Connection { 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()); @@ -265,10 +283,13 @@ impl telepathy::Connection for Connection { } } } + + 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()); @@ -276,10 +297,13 @@ impl telepathy::Connection for Connection { 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()); @@ -287,10 +311,13 @@ impl telepathy::Connection for Connection { 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()); @@ -298,6 +325,8 @@ impl telepathy::Connection for Connection { 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 @@ -306,14 +335,14 @@ impl telepathy::Connection for Connection { let mut w = state.write().unwrap(); *w = ConnState::Connected; - // Emit a NewConnection signal for the benefit of others, but the caller + // 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(CONN_OBJECT_PATH.to_string()) + let dbus_conn_path = dbus::strings::Path::new(self.path().to_string()) .expect("Object path should meet DBUS requirements"); self.msgq @@ -347,10 +376,7 @@ impl telepathy::Connection for Connection { fn get_interfaces(&self) -> Result, tree::MethodErr> { println!("Connection<{}>::get_interfaces()", self.id); - Ok(vec![ - "org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(), - ]) + Ok(connection_interfaces()) } fn get_protocol(&self) -> Result { @@ -368,7 +394,7 @@ impl telepathy::Connection for Connection { fn get_self_handle(&self) -> Result { println!("Connection<{}>::get_self_handle()", self.id); - Ok(deltachat::constants::DC_CONTACT_ID_SELF) + Ok(dc::constants::DC_CONTACT_ID_SELF) } fn status(&self) -> Result { @@ -389,7 +415,9 @@ impl telepathy::Connection for Connection { "Connection<{}>::hold_handles({}, {:?})", self.id, handle_type, handles ); - Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? + + // Since HasImmortalHandles is true, this doesn't need to do anything + Ok(()) } fn inspect_handles( @@ -416,7 +444,9 @@ impl telepathy::Connection for Connection { "Connection<{}>::release_handles({}, {:?})", self.id, handle_type, handles ); - Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? + + // Since HasImmortalHandles is true, we don't need to do anything + Ok(()) } fn request_channel( @@ -461,7 +491,18 @@ impl telepathy::Connection for Connection { fn self_id(&self) -> Result { println!("Connection<{}>::self_id()", self.id); - Ok("Yourself".to_string()) // FIXME: this could be passed through config + 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 { @@ -471,6 +512,107 @@ impl telepathy::Connection for 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), 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, @@ -488,23 +630,33 @@ impl telepathy::ConnectionInterfaceContacts for Connection { // 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, // FIXME: Ignore the error. Whatevs. + 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_display_name().to_string())), + arg::Variant(Box::new(contact.get_addr().to_string())), ); - // We don't advertise this interface but empathy is being weird. // 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); } @@ -532,7 +684,113 @@ impl telepathy::ConnectionInterfaceContacts for Connection { fn contact_attribute_interfaces(&self) -> Result, tree::MethodErr> { println!("Connection<{}>::contact_attribute_interfaces()", self.id); - Ok(vec![]) + 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 } } @@ -593,6 +851,68 @@ impl telepathy::ConnectionInterfaceRequests for 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<(), 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) } diff --git a/src/padfoot/connection_manager.rs b/src/padfoot/connection_manager.rs index 616f441..333eb6b 100644 --- a/src/padfoot/connection_manager.rs +++ b/src/padfoot/connection_manager.rs @@ -129,10 +129,7 @@ impl telepathy::ConnectionManager for ConnectionManager { props.insert( "org.freedesktop.Telepathy.Protocol.ConnectionInterfaces".to_string(), - arg::Variant(Box::new(vec![ - "org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(), - ])), + arg::Variant(Box::new(super::connection_interfaces())), ); props.insert( "org.freedesktop.Telepathy.Protocol.EnglishName".to_string(),