blob: 253b98d2d42aa4774d85505f85c0fbf8788c8bbe [file] [log] [blame]
/// Utility to watch the presence of a D-Bus services and interfaces.
use dbus::channel::MatchingReceiver;
use dbus::message::MatchRule;
use dbus::nonblock::stdintf::org_freedesktop_dbus::ObjectManager;
use dbus::nonblock::SyncConnection;
use std::sync::Arc;
use std::time::Duration;
const DBUS_SERVICE: &str = "org.freedesktop.DBus";
const DBUS_INTERFACE: &str = "org.freedesktop.DBus";
const DBUS_OBJMGR_INTERFACE: &str = "org.freedesktop.DBus.ObjectManager";
const DBUS_GET_NAME_OWNER: &str = "GetNameOwner";
const DBUS_NAME_OWNER_CHANGED: &str = "NameOwnerChanged";
const DBUS_INTERFACES_ADDED: &str = "InterfacesAdded";
const DBUS_PATH: &str = "/org/freedesktop/DBus";
const DBUS_TIMEOUT: Duration = Duration::from_secs(2);
pub struct ServiceWatcher {
conn: Arc<SyncConnection>,
service_name: String,
}
impl ServiceWatcher {
pub fn new(conn: Arc<SyncConnection>, service_name: String) -> Self {
ServiceWatcher { conn, service_name }
}
// Returns true if the named D-Bus service is available.
async fn is_service_available(&self) -> bool {
let dbus_proxy =
dbus::nonblock::Proxy::new(DBUS_SERVICE, DBUS_PATH, DBUS_TIMEOUT, self.conn.clone());
let service_owner: Result<(String,), dbus::Error> = dbus_proxy
.method_call(DBUS_INTERFACE, DBUS_GET_NAME_OWNER, (self.service_name.clone(),))
.await;
match service_owner {
Err(e) => {
log::debug!("Getting service owner failed: {}", e);
false
}
Ok((owner,)) => {
log::debug!("{} service owner = {}", self.service_name, owner);
true
}
}
}
// Returns the object path if the service exports an object having the specified interface.
async fn get_path_of_interface(&self, interface: String) -> Option<dbus::Path<'static>> {
let service_proxy = dbus::nonblock::Proxy::new(
self.service_name.clone(),
"/",
DBUS_TIMEOUT,
self.conn.clone(),
);
let objects = service_proxy.get_managed_objects().await;
match objects {
Err(e) => {
log::debug!("Failed getting managed objects: {}", e);
None
}
Ok(objects) => objects
.into_iter()
.find(|(_key, value)| value.contains_key(&interface))
.map(|(key, _value)| key),
}
}
async fn monitor_name_owner_changed(
&self,
on_available: Box<dyn Fn() + Send>,
on_unavailable: Box<dyn Fn() + Send>,
) {
let mr = MatchRule::new_signal(DBUS_INTERFACE, DBUS_NAME_OWNER_CHANGED);
self.conn.add_match_no_cb(&mr.match_str()).await.unwrap();
let service_name = self.service_name.clone();
self.conn.start_receive(
mr,
Box::new(move |msg, _conn| {
if let (Some(name), Some(old_owner), Some(new_owner)) =
msg.get3::<String, String, String>()
{
// Appearance/disappearance of unrelated service, ignore since we are not
// interested.
if name != service_name {
return true;
}
if old_owner == "" && new_owner != "" {
on_available();
} else if old_owner != "" && new_owner == "" {
on_unavailable();
} else {
log::warn!(
"Invalid NameOwnerChanged with old_owner = {} and new_owner = {}",
old_owner,
new_owner
);
}
}
true
}),
);
}
/// Watches appearance and disappearance of a D-Bus service by the name.
pub async fn start_watch(
&self,
on_available: Box<dyn Fn() + Send>,
on_unavailable: Box<dyn Fn() + Send>,
) {
if self.is_service_available().await {
// If service is already available at the start, just call the hook immediately.
on_available();
}
// Monitor service appearing and disappearing.
self.monitor_name_owner_changed(
Box::new(move || {
on_available();
}),
Box::new(move || {
on_unavailable();
}),
)
.await;
}
/// Watches the appearance of an interface of a service, and the disappearance of the service.
///
/// Doesn't take into account the disappearance of the interface itself. At the moment assuming
/// interfaces do not disappear as long as the service is alive.
pub async fn start_watch_interface(
&self,
interface: String,
on_available: Box<dyn Fn(dbus::Path<'static>) + Send>,
on_unavailable: Box<dyn Fn() + Send>,
) {
if self.is_service_available().await {
if let Some(path) = self.get_path_of_interface(interface.clone()).await {
on_available(path);
}
}
// Monitor service disappearing.
self.monitor_name_owner_changed(
Box::new(move || {
// Ignore service appearing because we rely on interface added.
}),
Box::new(move || {
on_unavailable();
}),
)
.await;
// Monitor interface appearing.
let mr = MatchRule::new_signal(DBUS_OBJMGR_INTERFACE, DBUS_INTERFACES_ADDED);
self.conn.add_match_no_cb(&mr.match_str()).await.unwrap();
self.conn.start_receive(
mr,
Box::new(move |msg, _conn| {
let (object_path, interfaces) =
msg.get2::<dbus::Path, dbus::arg::Dict<String, dbus::arg::PropMap, _>>();
let interfaces: Vec<String> = interfaces.unwrap().map(|e| e.0).collect();
if interfaces.contains(&interface) {
on_available(object_path.unwrap().into_static());
}
true
}),
);
}
}