diff --git a/kordophoned/src/daemon/contact_resolver/eds.rs b/kordophoned/src/daemon/contact_resolver/eds.rs index ceb9fc5..06abc40 100644 --- a/kordophoned/src/daemon/contact_resolver/eds.rs +++ b/kordophoned/src/daemon/contact_resolver/eds.rs @@ -5,7 +5,6 @@ use once_cell::sync::OnceCell; use std::collections::HashMap; use std::time::Duration; use std::sync::Mutex; -use std::thread; #[derive(Clone)] pub struct EDSContactResolverBackend; @@ -39,16 +38,40 @@ impl AddressBookHandle { /// Obtain the global address-book handle, initialising it on the first call. static ADDRESS_BOOK_HANDLE: OnceCell> = OnceCell::new(); -fn get_address_book_handle() -> Option<&'static Mutex> { - ADDRESS_BOOK_HANDLE +/// Check whether a given well-known name currently has an owner on the bus. +fn name_has_owner(conn: &Connection, name: &str) -> bool { + let proxy = conn.with_proxy("org.freedesktop.DBus", "/org/freedesktop/DBus", Duration::from_secs(2)); + let result: Result<(bool,), _> = proxy.method_call("org.freedesktop.DBus", "NameHasOwner", (name.to_string(),)); + result.map(|(b,)| b).unwrap_or(false) +} + +/// Returns a fresh handle, ensuring the cached one is still valid. If the backend owning the +/// address-book disappeared, the cache is cleared and we try to create a new handle. +fn obtain_handle() -> Option> { + // Initialize cell if necessary. + let cell = ADDRESS_BOOK_HANDLE .get_or_try_init(|| AddressBookHandle::new().map(Mutex::new)) - .map_err(|e| { - log::debug!( - "EDS resolver: failed to initialise address book handle: {}", - e - ); - }) - .ok() + .ok()?; + + // Validate existing handle. + { + let mut guard = cell.lock().ok()?; + if !name_has_owner(&guard.connection, &guard.bus_name) { + // Try to refresh the handle in-place. + match AddressBookHandle::new() { + Ok(new_h) => { + *guard = new_h; + } + Err(e) => { + log::debug!("EDS resolver: failed to refresh address book handle: {}", e); + // keep the stale handle but report failure + return None; + } + } + } + // Return guard after ensuring validity. + return Some(guard); + } } /// Helper that returns a blocking D-Bus session connection. Creating the @@ -144,22 +167,35 @@ fn open_address_book( Ok((object_path, bus_name)) } +/// Ensure that the backend for the given address-book proxy is opened. +/// Evolution-Data-Server returns "Backend is not opened yet" until someone +/// calls the `Open` method once per process. We ignore any error here +/// because the backend might already be open. +fn ensure_address_book_open(proxy: &dbus::blocking::Proxy<&Connection>) { + let _: Result<(), _> = proxy.method_call( + "org.gnome.evolution.dataserver.AddressBook", + "Open", + (), + ); +} + impl ContactResolverBackend for EDSContactResolverBackend { type ContactID = String; fn resolve_contact_id(&self, address: &str) -> Option { - let handle_mutex = match get_address_book_handle() { + let handle = match obtain_handle() { Some(h) => h, None => return None, }; - let handle = handle_mutex.lock().unwrap(); let address_book_proxy = handle.connection.with_proxy( &handle.bus_name, &handle.object_path, Duration::from_secs(60), ); + ensure_address_book_open(&address_book_proxy); + let filter = if address.contains('@') { format!("(is \"email\" \"{}\")", address) } else { @@ -207,18 +243,19 @@ impl ContactResolverBackend for EDSContactResolverBackend { } fn get_contact_display_name(&self, contact_id: &Self::ContactID) -> Option { - let handle_mutex = match get_address_book_handle() { + let handle = match obtain_handle() { Some(h) => h, None => return None, }; - let handle = handle_mutex.lock().unwrap(); let address_book_proxy = handle.connection.with_proxy( &handle.bus_name, &handle.object_path, Duration::from_secs(60), ); + ensure_address_book_open(&address_book_proxy); + let vcard_result: Result<(String,), _> = address_book_proxy.method_call( "org.gnome.evolution.dataserver.AddressBook", "GetContact",