use std::{collections::HashSet, convert::TryInto, marker::PhantomData, sync::Arc}; use static_assertions::assert_impl_all; use zbus_names::{BusName, InterfaceName}; use zvariant::{ObjectPath, Str}; use crate::{Connection, Error, Proxy, ProxyInner, Result}; /// The properties caching mode. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum CacheProperties { /// Cache properties. The properties will be cached upfront as part of the proxy /// creation. Yes, /// Don't cache properties. No, /// Cache properties but only populate the cache on the first read of a property (default). #[default] Lazily, } /// Builder for proxies. #[derive(Debug)] pub struct ProxyBuilder<'a, T = ()> { conn: Connection, destination: Option>, path: Option>, interface: Option>, proxy_type: PhantomData, cache: CacheProperties, uncached_properties: Option>>, } impl<'a, T> Clone for ProxyBuilder<'a, T> { fn clone(&self) -> Self { Self { conn: self.conn.clone(), destination: self.destination.clone(), path: self.path.clone(), interface: self.interface.clone(), cache: self.cache, uncached_properties: self.uncached_properties.clone(), proxy_type: PhantomData, } } } assert_impl_all!(ProxyBuilder<'_>: Send, Sync, Unpin); impl<'a, T> ProxyBuilder<'a, T> { /// Create a new [`ProxyBuilder`] for the given connection. #[must_use] pub fn new_bare(conn: &Connection) -> Self { Self { conn: conn.clone(), destination: None, path: None, interface: None, cache: CacheProperties::default(), uncached_properties: None, proxy_type: PhantomData, } } } impl<'a, T> ProxyBuilder<'a, T> { /// Set the proxy destination address. pub fn destination(mut self, destination: D) -> Result where D: TryInto>, D::Error: Into, { self.destination = Some(destination.try_into().map_err(Into::into)?); Ok(self) } /// Set the proxy path. pub fn path

(mut self, path: P) -> Result where P: TryInto>, P::Error: Into, { self.path = Some(path.try_into().map_err(Into::into)?); Ok(self) } /// Set the proxy interface. pub fn interface(mut self, interface: I) -> Result where I: TryInto>, I::Error: Into, { self.interface = Some(interface.try_into().map_err(Into::into)?); Ok(self) } /// Set the properties caching mode. #[must_use] pub fn cache_properties(mut self, cache: CacheProperties) -> Self { self.cache = cache; self } /// Specify a set of properties (by name) which should be excluded from caching. #[must_use] pub fn uncached_properties(mut self, properties: &[&'a str]) -> Self { self.uncached_properties .replace(properties.iter().map(|p| Str::from(*p)).collect()); self } pub(crate) fn build_internal(self) -> Result> { let conn = self.conn; let destination = self .destination .ok_or(Error::MissingParameter("destination"))?; let path = self.path.ok_or(Error::MissingParameter("path"))?; let interface = self.interface.ok_or(Error::MissingParameter("interface"))?; let cache = self.cache; let uncached_properties = self.uncached_properties.unwrap_or_default(); Ok(Proxy { inner: Arc::new(ProxyInner::new( conn, destination, path, interface, cache, uncached_properties, )), }) } /// Build a proxy from the builder. /// /// # Errors /// /// If the builder is lacking the necessary parameters to build a proxy, /// [`Error::MissingParameter`] is returned. pub async fn build(self) -> Result where T: From>, { let cache_upfront = self.cache == CacheProperties::Yes; let proxy = self.build_internal()?; if cache_upfront { proxy .get_property_cache() .expect("properties cache not initialized") .ready() .await?; } Ok(proxy.into()) } } impl<'a, T> ProxyBuilder<'a, T> where T: ProxyDefault, { /// Create a new [`ProxyBuilder`] for the given connection. #[must_use] pub fn new(conn: &Connection) -> Self { Self { conn: conn.clone(), destination: Some(BusName::from_static_str(T::DESTINATION).expect("invalid bus name")), path: Some(ObjectPath::from_static_str(T::PATH).expect("invalid default path")), interface: Some( InterfaceName::from_static_str(T::INTERFACE).expect("invalid interface name"), ), cache: CacheProperties::default(), uncached_properties: None, proxy_type: PhantomData, } } } /// Trait for the default associated values of a proxy. /// /// The trait is automatically implemented by the [`dbus_proxy`] macro on your behalf, and may be /// later used to retrieve the associated constants. /// /// [`dbus_proxy`]: attr.dbus_proxy.html pub trait ProxyDefault { const INTERFACE: &'static str; const DESTINATION: &'static str; const PATH: &'static str; } #[cfg(test)] mod tests { use super::*; use test_log::test; #[test] #[ntest::timeout(15000)] fn builder() { crate::utils::block_on(builder_async()); } async fn builder_async() { let conn = Connection::session().await.unwrap(); let builder = ProxyBuilder::>::new_bare(&conn) .destination("org.freedesktop.DBus") .unwrap() .path("/some/path") .unwrap() .interface("org.freedesktop.Interface") .unwrap() .cache_properties(CacheProperties::No); assert!(matches!( builder.clone().destination.unwrap(), BusName::Unique(_), )); let proxy = builder.build().await.unwrap(); assert!(matches!(proxy.inner.destination, BusName::Unique(_))); } }