use std::{
borrow::Cow,
collections::HashMap,
convert::{TryFrom, TryInto},
sync::Mutex,
};
use zvariant::{ObjectPath, OwnedValue, Value};
use crate::{Connection, Error, Message, Result};
use crate::fdo::{self, IntrospectableProxy, PropertiesProxy};
const LOCK_FAIL_MSG: &str = "Failed to lock a mutex or read-write lock";
type SignalHandler = Box<dyn FnMut(&Message) -> Result<()> + Send>;
pub struct Proxy<'a> {
conn: Connection,
destination: Cow<'a, str>,
path: Cow<'a, str>,
interface: Cow<'a, str>,
sig_handlers: Mutex<HashMap<&'static str, SignalHandler>>,
}
impl<'a> Proxy<'a> {
pub fn new(
conn: &Connection,
destination: &'a str,
path: &'a str,
interface: &'a str,
) -> Result<Self> {
Ok(Self {
conn: conn.clone(),
destination: Cow::from(destination),
path: Cow::from(path),
interface: Cow::from(interface),
sig_handlers: Mutex::new(HashMap::new()),
})
}
pub fn new_owned(
conn: Connection,
destination: String,
path: String,
interface: String,
) -> Result<Self> {
Ok(Self {
conn,
destination: Cow::from(destination),
path: Cow::from(path),
interface: Cow::from(interface),
sig_handlers: Mutex::new(HashMap::new()),
})
}
pub fn connection(&self) -> &Connection {
&self.conn
}
pub fn destination(&self) -> &str {
&self.destination
}
pub fn path(&self) -> &str {
&self.path
}
pub fn interface(&self) -> &str {
&self.interface
}
pub fn introspect(&self) -> fdo::Result<String> {
IntrospectableProxy::new_for(&self.conn, &self.destination, &self.path)?.introspect()
}
pub fn get_property<T>(&self, property_name: &str) -> fdo::Result<T>
where
T: TryFrom<OwnedValue>,
{
PropertiesProxy::new_for(&self.conn, &self.destination, &self.path)?
.get(&self.interface, property_name)?
.try_into()
.map_err(|_| Error::InvalidReply.into())
}
pub fn set_property<'t, T: 't>(&self, property_name: &str, value: T) -> fdo::Result<()>
where
T: Into<Value<'t>>,
{
PropertiesProxy::new_for(&self.conn, &self.destination, &self.path)?.set(
&self.interface,
property_name,
&value.into(),
)
}
pub fn call_method<B>(&self, method_name: &str, body: &B) -> Result<Message>
where
B: serde::ser::Serialize + zvariant::Type,
{
let reply = self.conn.call_method(
Some(&self.destination),
&self.path,
Some(&self.interface),
method_name,
body,
);
match reply {
Ok(mut reply) => {
reply.disown_fds();
Ok(reply)
}
Err(e) => Err(e),
}
}
pub fn call<B, R>(&self, method_name: &str, body: &B) -> Result<R>
where
B: serde::ser::Serialize + zvariant::Type,
R: serde::de::DeserializeOwned + zvariant::Type,
{
Ok(self.call_method(method_name, body)?.body()?)
}
pub fn connect_signal<H>(&self, signal_name: &'static str, handler: H) -> fdo::Result<()>
where
H: FnMut(&Message) -> Result<()> + Send + 'static,
{
if self
.sig_handlers
.lock()
.expect(LOCK_FAIL_MSG)
.insert(signal_name, Box::new(handler))
.is_none()
&& self.conn.is_bus()
{
let rule = self.match_rule_for_signal(signal_name);
fdo::DBusProxy::new(&self.conn)?.add_match(&rule)?;
}
Ok(())
}
pub fn disconnect_signal(&self, signal_name: &'static str) -> fdo::Result<bool> {
if self
.sig_handlers
.lock()
.expect(LOCK_FAIL_MSG)
.remove(signal_name)
.is_some()
&& self.conn.is_bus()
{
let rule = self.match_rule_for_signal(signal_name);
fdo::DBusProxy::new(&self.conn)?.remove_match(&rule)?;
Ok(true)
} else {
Ok(false)
}
}
pub fn next_signal(&self) -> Result<Option<Message>> {
let msg = self.conn.receive_specific(|msg| {
let handlers = self.sig_handlers.lock().expect(LOCK_FAIL_MSG);
if handlers.is_empty() {
return Ok(true);
}
let hdr = msg.header()?;
let member = match hdr.member()? {
Some(m) => m,
None => return Ok(false),
};
Ok(hdr.interface()? == Some(&self.interface)
&& hdr.path()? == Some(&ObjectPath::try_from(self.path.as_ref())?)
&& hdr.message_type()? == crate::MessageType::Signal
&& handlers.contains_key(member))
})?;
if self.handle_signal(&msg)? {
Ok(None)
} else {
Ok(Some(msg))
}
}
pub fn handle_signal(&self, msg: &Message) -> Result<bool> {
let mut handlers = self.sig_handlers.lock().expect(LOCK_FAIL_MSG);
if handlers.is_empty() {
return Ok(false);
}
let hdr = msg.header()?;
if let Some(name) = hdr.member()? {
if let Some(handler) = handlers.get_mut(name) {
handler(&msg)?;
return Ok(true);
}
}
Ok(false)
}
pub(crate) fn has_signal_handler(&self, signal_name: &str) -> bool {
self.sig_handlers
.lock()
.expect(LOCK_FAIL_MSG)
.contains_key(signal_name)
}
fn match_rule_for_signal(&self, signal_name: &'static str) -> String {
format!(
"type='signal',path_namespace='{}',interface='{}',member='{}'",
self.path, self.interface, signal_name,
)
}
}
impl<'asref, 'p: 'asref> std::convert::AsRef<Proxy<'asref>> for Proxy<'p> {
fn as_ref(&self) -> &Proxy<'asref> {
&self
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn signal() {
let conn = Connection::new_session().unwrap();
let owner_change_signaled = Arc::new(Mutex::new(false));
let name_acquired_signaled = Arc::new(Mutex::new(false));
let proxy = Proxy::new(
&conn,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
)
.unwrap();
let well_known = "org.freedesktop.zbus.ProxySignalTest";
let unique_name = conn.unique_name().unwrap().to_string();
{
let well_known = well_known.clone();
let signaled = owner_change_signaled.clone();
proxy
.connect_signal("NameOwnerChanged", move |m| {
let (name, _, new_owner) = m.body::<(&str, &str, &str)>()?;
if name != well_known {
return Ok(());
}
assert_eq!(new_owner, unique_name);
*signaled.lock().unwrap() = true;
Ok(())
})
.unwrap();
}
{
let signaled = name_acquired_signaled.clone();
proxy
.connect_signal("NameAcquired", move |m| {
if m.body::<&str>()? == well_known {
*signaled.lock().unwrap() = true;
}
Ok(())
})
.unwrap();
}
fdo::DBusProxy::new(&conn)
.unwrap()
.request_name(&well_known, fdo::RequestNameFlags::ReplaceExisting.into())
.unwrap();
loop {
proxy.next_signal().unwrap();
if *owner_change_signaled.lock().unwrap() && *name_acquired_signaled.lock().unwrap() {
break;
}
}
}
}