diff --git a/src/padfoot/channel.rs b/src/padfoot/channel.rs index 66d87e2..a6aadec 100644 --- a/src/padfoot/channel.rs +++ b/src/padfoot/channel.rs @@ -8,10 +8,12 @@ pub use messages::*; mod type_text; pub use type_text::*; -use crate::padfoot::DbusAction; +use crate::padfoot::{var_bool, var_str, var_str_vec, var_u32, DbusAction, VarArg}; use crate::telepathy; use deltachat as dc; + +use std::collections::HashMap; use std::sync::{mpsc, Arc, RwLock}; type Result = std::result::Result; @@ -34,6 +36,7 @@ pub struct Channel { actq: mpsc::Sender, chat_id: dc::chat::ChatId, ctx: Arc>, + initiator_handle: u32, path: dbus::Path<'static>, requested: bool, target_handle: u32, // Who we're talking to @@ -49,6 +52,7 @@ impl Channel { actq: mpsc::Sender, chat_id: dc::chat::ChatId, ctx: Arc>, + initiator_handle: u32, path: dbus::Path<'static>, requested: bool, target_handle: u32, @@ -57,12 +61,46 @@ impl Channel { actq, chat_id, ctx, + initiator_handle, path, requested, target_handle, } } + // FIXME: we should be able to introspect this already??? + pub fn chan_props(&self) -> HashMap { + let mut out = HashMap::::new(); + out.insert( + "org.freedesktop.Telepathy.Channel.ChannelType".to_string(), + var_str(self.chan_type()), + ); + out.insert( + "org.freedesktop.Telepathy.Channel.TargetHandleType".to_string(), + var_u32(self.handle_type()), + ); + out.insert( + "org.freedesktop.Telepathy.Channel.TargetHandle".to_string(), + var_u32(self.target_handle), + ); + out.insert( + "org.freedesktop.Telepathy.Channel.TargetID".to_string(), + var_str(self.target_contact().unwrap().get_addr().to_string()), + ); + out.insert( + "org.freedesktop.Telepathy.Channel.Requested".to_string(), + var_bool(self.requested), + ); + out.insert( + "org.freedesktop.Telepathy.Channel.Interfaces".to_string(), + var_str_vec(vec![ + "org.freedesktop.Telepathy.Channel.Interface.Messages".to_string() + ]), + ); + + out + } + pub fn path(&self) -> dbus::Path<'static> { self.path.clone() } @@ -88,7 +126,7 @@ impl Channel { pub fn initiator_contact(&self) -> Option { let ctx = self.ctx.read().unwrap(); - dc::contact::Contact::get_by_id(&ctx, self.handle()).ok() // FIXME: this will be wrong for outbound channels + dc::contact::Contact::get_by_id(&ctx, self.initiator_handle).ok() // FIXME: this will be wrong for outbound channels } pub fn requested(&self) -> bool { diff --git a/src/padfoot/connection.rs b/src/padfoot/connection.rs index 584544e..0af1399 100644 --- a/src/padfoot/connection.rs +++ b/src/padfoot/connection.rs @@ -20,7 +20,7 @@ pub use self::requests::*; mod simple_presence; pub use self::simple_presence::*; -use crate::padfoot::{convert_msg, var_bool, var_str, var_str_vec, var_u32, Channel, VarArg}; +use crate::padfoot::{convert_msg, Channel, VarArg}; use crate::telepathy; use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection}; @@ -317,12 +317,11 @@ impl Connection { } } DbusAction::NewChannel(channel) => { - let requested = channel.requested(); let chan_type = channel.chan_type(); let handle_type = channel.handle_type(); let handle = channel.handle(); - let target_id = channel.target_contact().unwrap().get_addr().to_string(); let chan_path = channel.path().clone(); + let chan_props = channel.chan_props(); let rc_channel = Arc::new(channel); println!("*** Creating channel {}", chan_path); @@ -337,34 +336,6 @@ impl Connection { t2.lock().unwrap().insert(op); - let mut chan_props = HashMap::::new(); - chan_props.insert( - "org.freedesktop.Telepathy.Channel.ChannelType".to_string(), - var_str(chan_type.clone()), - ); - chan_props.insert( - "org.freedesktop.Telepathy.Channel.TargetHandleType".to_string(), - var_u32(handle_type), - ); - chan_props.insert( - "org.freedesktop.Telepathy.Channel.TargetHandle".to_string(), - var_u32(handle), - ); - chan_props.insert( - "org.freedesktop.Telepathy.Channel.TargetID".to_string(), - var_str(target_id), - ); - chan_props.insert( - "org.freedesktop.Telepathy.Channel.Requested".to_string(), - var_bool(requested), - ); - chan_props.insert( - "org.freedesktop.Telepathy.Channel.Interfaces".to_string(), - var_str_vec(vec![ - "org.freedesktop.Telepathy.Channel.Interface.Messages".to_string(), - ]), - ); - let requests_sig = telepathy::ConnectionInterfaceRequestsNewChannels { channels: vec![(chan_path.clone(), chan_props)], }; @@ -403,12 +374,7 @@ impl Connection { DbusAction::IncomingMessage(chat_id, msg_id) => { println!("*** Incoming message: {} {}", chat_id, msg_id); // TODO: check if we have a channel for the chat - let chan_path = dbus::strings::Path::new(format!( - "{}/{}", - path.clone(), - chat_id.to_u32() - )) - .unwrap(); + let chan_path = Connection::build_channel_path(path.clone(), chat_id); let c2 = Arc::clone(&chans); let chans = c2.read().unwrap(); @@ -458,9 +424,10 @@ impl Connection { actq.clone(), chat_id, ctx.clone(), + *handle, // initiator chan_path, false, // FIXME: this needs to handle requested channels - *handle, + dc::constants::DC_CONTACT_ID_SELF, // target is us ); actq.send(DbusAction::NewChannel(chan)).unwrap(); actq.send(act).unwrap(); @@ -510,6 +477,15 @@ impl Connection { self.settings.path() } + fn build_channel_path( + path: dbus::Path<'static>, + chat_id: dc::chat::ChatId, + ) -> dbus::strings::Path<'static> { + let path = format!("{}/{}", path, chat_id.to_u32()); + + dbus::strings::Path::new(path).expect("Must be valid") + } + fn build_tree(self) -> Arc>> { let path = self.path(); let c_rc = std::rc::Rc::new(self); @@ -528,7 +504,7 @@ impl Connection { telepathy::connection_interface_contacts_server(&f, (), move |_| c_rc3.clone()); let _c_rc4 = c_rc.clone(); - let _contact_list_iface = + let contact_list_iface = telepathy::connection_interface_contact_list_server(&f, (), move |_| _c_rc4.clone()); let c_rc5 = c_rc.clone(); @@ -544,7 +520,7 @@ impl Connection { .add(conn_iface) .add(avatars_iface) .add(contacts_iface) - // .add(contact_list_iface) + .add(contact_list_iface) .add(requests_iface) .add(simple_presence_iface), ); diff --git a/src/padfoot/connection/connection.rs b/src/padfoot/connection/connection.rs index 86f1b16..b644a47 100644 --- a/src/padfoot/connection/connection.rs +++ b/src/padfoot/connection/connection.rs @@ -1,8 +1,13 @@ use crate::telepathy; use crate::telepathy::{ConnectionInterfaceContacts, ConnectionInterfaceRequests}; // Non-deprecated channel methods + use dbus::message::SignalArgs; use dbus::tree::MethodErr; +use dc::contact::Contact; use deltachat as dc; + +use std::collections::HashMap; +use std::convert::TryInto; use std::thread; use super::{Connection, DbusAction}; @@ -24,12 +29,18 @@ type ChannelInfo = ( u32, // Handle ); +type ContactSubscription = ( + u32, // Subscribe state + u32, // Publish state + String, // Publish-request message +); + 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.ContactList".to_string(), "org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(), "org.freedesktop.Telepathy.Connection.Interface.SimplePresence".to_string(), ] @@ -111,19 +122,49 @@ impl telepathy::Connection for Connection { let state = self.state.clone(); let mut w = state.write().unwrap(); *w = ConnState::Connected; + let ctx = self.ctx.read().unwrap(); // Emit a StatusChanged signal for the benefit of others, but the caller // learns from our RPC response - let sig = telepathy::ConnectionStatusChanged { + let connected_sig = telepathy::ConnectionStatusChanged { status: 0, // Connected reason: 1, // Requested - }; + } + .to_emit_message(&self.path()); - self.actq - .send(DbusAction::Signal(sig.to_emit_message(&self.path()))) - .unwrap(); + self.actq.send(DbusAction::Signal(connected_sig)).unwrap(); self.actq.send(DbusAction::FreshMessages).unwrap(); + // If we can, emit signals on connect about the contact list + if let Ok(handles) = Contact::get_all( + &ctx, + (dc::constants::DC_GCL_ADD_SELF as usize) + .try_into() + .unwrap(), + None::, + ) { + println!(" HANDLES: {:?}", handles); + let mut changes = HashMap::::new(); + for handle in handles { + println!(" *** Handle: {}", handle); + changes.insert(handle, (4, 4, "".to_string())); // FIXME: hardcoded lies + } + + // TODO: the old signal is deprecated. The new signal requires us to + // send identifiers with it, which is a bit of a lookup here. + // let cl_sig_new = telepathy::ConnectionInterfaceContactsChangedWithID { + // }.to_emit_message(&self.path()); + + let cl_sig_old = telepathy::ConnectionInterfaceContactListContactsChanged { + changes, + removals: Vec::new(), + } + .to_emit_message(&self.path()); + + // self.actq.send(DbusAction::Signal(cl_sig_new)).unwrap(); + self.actq.send(DbusAction::Signal(cl_sig_old)).unwrap(); + }; + Ok(()) } @@ -287,7 +328,39 @@ impl telepathy::Connection for Connection { identifiers ); - Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + match handle_type { + crate::padfoot::HANDLE_TYPE_CONTACT => { + let ctx = self.ctx.read().unwrap(); + let mut out = Vec::::new(); + + // Identifiers is a list of email addresses. These can be + // contact IDs, although we may have to create contacts to get + // the ID. + // + // FIXME: will it be faster to get all and filter? + + for addr in identifiers { + let id = Contact::lookup_id_by_addr(&ctx, addr, dc::contact::Origin::Unknown); + match id { + 0 => { + // No contact exists for this address yet. Try to + // add one so we can have an ID. + match Contact::create(&ctx, &addr, &addr) { + Ok(new_id) => out.push(new_id), + Err(e) => { + println!("Failed to add contact {}: {}", addr, e); + return Err(MethodErr::no_arg()); + } + } + } + _ => out.push(id), + } + } + + Ok(out) + } + _ => Err(MethodErr::no_arg()), // FIXME: should be NotImplemented? + } } fn add_client_interest(&self, tokens: Vec<&str>) -> Result<()> { diff --git a/src/padfoot/connection/contact_list.rs b/src/padfoot/connection/contact_list.rs index 326c251..7cc32df 100644 --- a/src/padfoot/connection/contact_list.rs +++ b/src/padfoot/connection/contact_list.rs @@ -39,6 +39,7 @@ impl telepathy::ConnectionInterfaceContactList for Connection { } }; + // FIXME: swap implementations so Contacts depends on ContactList? self.get_contact_attributes(ids, vec![], true) } @@ -76,6 +77,8 @@ impl telepathy::ConnectionInterfaceContactList for Connection { fn download(&self) -> Result<(), MethodErr> { println!("Connection<{}>::download()", self.id()); + + // This can be a no-op since we store the contacts list in delta's DB Ok(()) } @@ -91,7 +94,7 @@ impl telepathy::ConnectionInterfaceContactList for Connection { fn can_change_contact_list(&self) -> Result { println!("Connection<{}>::can_change_contact_list()", self.id()); - Ok(false) + Ok(true) } fn request_uses_message(&self) -> Result { println!("Connection<{}>::request_uses_message()", self.id()); @@ -100,6 +103,11 @@ impl telepathy::ConnectionInterfaceContactList for Connection { fn download_at_connection(&self) -> Result { println!("Connection<{}>::download_at_connection()", self.id()); - Ok(false) + + // TODO: https://telepathy.freedesktop.org/spec/Connection_Interface_Contact_List.html#Property:DownloadAtConnection + // Connections ... SHOULD provide a corresponding parameter named + // org.freedesktop.Telepathy.Connection.Interface.ContactList.DownloadAtConnection + // with the DBus_Property flag + Ok(true) } } diff --git a/src/padfoot/connection/contacts.rs b/src/padfoot/connection/contacts.rs index 824b2e4..2272c6d 100644 --- a/src/padfoot/connection/contacts.rs +++ b/src/padfoot/connection/contacts.rs @@ -1,4 +1,4 @@ -use crate::padfoot::{var_str, VarArg}; +use crate::padfoot::{var_str, var_u32, VarArg}; use crate::telepathy; use dbus::tree::MethodErr; @@ -48,18 +48,17 @@ impl telepathy::ConnectionInterfaceContacts for Connection { "org.freedesktop.Telepathy.Connection.Interface.Avatars/token".to_string(), var_str("".to_string()), ); - /* - // TODO: we need to publish DBUS services on these endpoints - props.insert( - "org.freedesktop.Telepathy.Connection.Interface.ContactList/publish".to_string(), - var_arg(Box::new(4)), - ); - props.insert( - "org.freedesktop.Telepathy.Connection.Interface.ContactList/subscribe".to_string(), - var_arg(Box::new(4)), - ); - */ + props.insert( + "org.freedesktop.Telepathy.Connection.Interface.ContactList/publish".to_string(), + var_u32(4), // YES (faking it for now) + ); + + props.insert( + "org.freedesktop.Telepathy.Connection.Interface.ContactList/subscribe".to_string(), + var_u32(4), // YES (faking it for now) + ); + out.insert(*id, props); } @@ -101,7 +100,7 @@ impl telepathy::ConnectionInterfaceContacts for Connection { Ok(vec![ "org.freedesktop.Telepathy.Connection".to_string(), "org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(), - // "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(), ]) } } diff --git a/src/padfoot/connection/requests.rs b/src/padfoot/connection/requests.rs index 139b632..a62acf7 100644 --- a/src/padfoot/connection/requests.rs +++ b/src/padfoot/connection/requests.rs @@ -1,7 +1,10 @@ -use crate::padfoot::{requestables, VarArg}; +use crate::padfoot::{get_var_str, get_var_u32, requestables, Channel, DbusAction, VarArg}; use crate::telepathy; use dbus::tree::MethodErr; +use dc::contact::Contact; +use deltachat as dc; + use std::collections::HashMap; use super::Connection; @@ -35,8 +38,80 @@ impl telepathy::ConnectionInterfaceRequests for Connection { &self, request: HashMap<&str, VarArg>, ) -> Result<(bool, dbus::Path<'static>, HashMap)> { + let path = self.path().clone(); println!("Connection<{}>::ensure_channel({:?})", self.id(), request); - Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? + + // Empathy sends this for the request: + // + // { + // "org.freedesktop.Telepathy.Channel.TargetID": Variant("me@example.com"), + // "org.freedesktop.Telepathy.Channel.TargetHandleType": Variant(1), + // "org.freedesktop.Telepathy.Channel.ChannelType": Variant("org.freedesktop.Telepathy.Channel.Type.Text") + // } + + // FIXME: life would be easier with TargetHandle + let target_id = request + .get("org.freedesktop.Telepathy.Channel.TargetID") + .map(|va| get_var_str(va)) + .unwrap(); + + let target_handle_type = request + .get("org.freedesktop.Telepathy.Channel.TargetHandleType") + .map(|va| get_var_u32(va)) + .unwrap(); + + let channel_type = request + .get("org.freedesktop.Telepathy.Channel.ChannelType") + .map(|va| get_var_str(va)) + .unwrap(); + + // Text only + if channel_type != "org.freedesktop.Telepathy.Channel.Type.Text" { + println!(" Wrong channel type: {:?}", channel_type); + return Err(MethodErr::no_arg()); + }; + + // IMs only + if target_handle_type != 1 { + println!(" Wrong target handle type: {:?}", target_handle_type); + return Err(MethodErr::no_arg()); + }; + + let ctx = self.ctx.read().unwrap(); + + let target_handle = + Contact::lookup_id_by_addr(&ctx, target_id.clone(), dc::contact::Origin::Unknown); + if target_handle == 0 { + println!("Couldn't find target handle for {}", target_id); + return Err(MethodErr::no_arg()); + }; + + let chat_id = dc::chat::ChatId::new(target_handle); + let channel_path = Connection::build_channel_path(path, chat_id); + + // Return an existing channel if it already exists + let chans = self.channels.read().unwrap(); + if let Some(channel) = chans.get(&channel_path) { + return Ok((false, channel_path, channel.chan_props())); + } + + // Now we need to discover or create a chat id for the contact + let chat_id = dc::chat::create_by_contact_id(&ctx, target_handle).unwrap(); + let channel = Channel::new( + self.actq.clone(), + chat_id, + self.ctx.clone(), + dc::constants::DC_CONTACT_ID_SELF, // initiator is self + channel_path.clone(), + true, // requested + target_handle, + ); + let response = channel.chan_props(); // FIXME: fill with data about the channel + + // Send signal + self.actq.send(DbusAction::NewChannel(channel)).unwrap(); + + Ok((true, channel_path, response)) } fn channels(&self) -> Result> { diff --git a/src/padfoot/var_arg.rs b/src/padfoot/var_arg.rs index 9f769b6..1ee99d1 100644 --- a/src/padfoot/var_arg.rs +++ b/src/padfoot/var_arg.rs @@ -1,5 +1,7 @@ use dbus::arg::{RefArg, Variant}; +use std::convert::TryInto; + pub type VarArg = Variant>; pub fn var_arg(item: Box) -> VarArg { @@ -25,3 +27,11 @@ pub fn var_u32(item: u32) -> VarArg { pub fn var_i64(item: i64) -> VarArg { var_arg(Box::new(item)) } + +pub fn get_var_str(from: &VarArg) -> String { + from.0.as_str().unwrap().to_string() +} + +pub fn get_var_u32(from: &VarArg) -> u32 { + from.0.as_u64().unwrap().try_into().unwrap() +}