| 1 | use std::{collections::HashSet, marker::PhantomData, sync::Arc}; |
| 2 | |
| 3 | use static_assertions::assert_impl_all; |
| 4 | use zbus_names::{BusName, InterfaceName}; |
| 5 | use zvariant::{ObjectPath, Str}; |
| 6 | |
| 7 | use crate::{proxy::ProxyInner, Connection, Error, Proxy, Result}; |
| 8 | |
| 9 | /// The properties caching mode. |
| 10 | #[derive (Debug, Default, Clone, Copy, PartialEq, Eq)] |
| 11 | #[non_exhaustive ] |
| 12 | pub enum CacheProperties { |
| 13 | /// Cache properties. The properties will be cached upfront as part of the proxy |
| 14 | /// creation. |
| 15 | Yes, |
| 16 | /// Don't cache properties. |
| 17 | No, |
| 18 | /// Cache properties but only populate the cache on the first read of a property (default). |
| 19 | #[default] |
| 20 | Lazily, |
| 21 | } |
| 22 | |
| 23 | /// Builder for proxies. |
| 24 | #[derive (Debug)] |
| 25 | pub struct Builder<'a, T = ()> { |
| 26 | conn: Connection, |
| 27 | destination: Option<BusName<'a>>, |
| 28 | path: Option<ObjectPath<'a>>, |
| 29 | interface: Option<InterfaceName<'a>>, |
| 30 | proxy_type: PhantomData<T>, |
| 31 | cache: CacheProperties, |
| 32 | uncached_properties: Option<HashSet<Str<'a>>>, |
| 33 | } |
| 34 | |
| 35 | impl<'a, T> Clone for Builder<'a, T> { |
| 36 | fn clone(&self) -> Self { |
| 37 | Self { |
| 38 | conn: self.conn.clone(), |
| 39 | destination: self.destination.clone(), |
| 40 | path: self.path.clone(), |
| 41 | interface: self.interface.clone(), |
| 42 | cache: self.cache, |
| 43 | uncached_properties: self.uncached_properties.clone(), |
| 44 | proxy_type: PhantomData, |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | assert_impl_all!(Builder<'_>: Send, Sync, Unpin); |
| 50 | |
| 51 | impl<'a, T> Builder<'a, T> { |
| 52 | /// Set the proxy destination address. |
| 53 | pub fn destination<D>(mut self, destination: D) -> Result<Self> |
| 54 | where |
| 55 | D: TryInto<BusName<'a>>, |
| 56 | D::Error: Into<Error>, |
| 57 | { |
| 58 | self.destination = Some(destination.try_into().map_err(Into::into)?); |
| 59 | Ok(self) |
| 60 | } |
| 61 | |
| 62 | /// Set the proxy path. |
| 63 | pub fn path<P>(mut self, path: P) -> Result<Self> |
| 64 | where |
| 65 | P: TryInto<ObjectPath<'a>>, |
| 66 | P::Error: Into<Error>, |
| 67 | { |
| 68 | self.path = Some(path.try_into().map_err(Into::into)?); |
| 69 | Ok(self) |
| 70 | } |
| 71 | |
| 72 | /// Set the proxy interface. |
| 73 | pub fn interface<I>(mut self, interface: I) -> Result<Self> |
| 74 | where |
| 75 | I: TryInto<InterfaceName<'a>>, |
| 76 | I::Error: Into<Error>, |
| 77 | { |
| 78 | self.interface = Some(interface.try_into().map_err(Into::into)?); |
| 79 | Ok(self) |
| 80 | } |
| 81 | |
| 82 | /// Set the properties caching mode. |
| 83 | #[must_use ] |
| 84 | pub fn cache_properties(mut self, cache: CacheProperties) -> Self { |
| 85 | self.cache = cache; |
| 86 | self |
| 87 | } |
| 88 | |
| 89 | /// Specify a set of properties (by name) which should be excluded from caching. |
| 90 | #[must_use ] |
| 91 | pub fn uncached_properties(mut self, properties: &[&'a str]) -> Self { |
| 92 | self.uncached_properties |
| 93 | .replace(properties.iter().map(|p| Str::from(*p)).collect()); |
| 94 | |
| 95 | self |
| 96 | } |
| 97 | |
| 98 | pub(crate) fn build_internal(self) -> Result<Proxy<'a>> { |
| 99 | let conn = self.conn; |
| 100 | let destination = self |
| 101 | .destination |
| 102 | .ok_or(Error::MissingParameter("destination" ))?; |
| 103 | let path = self.path.ok_or(Error::MissingParameter("path" ))?; |
| 104 | let interface = self.interface.ok_or(Error::MissingParameter("interface" ))?; |
| 105 | let cache = self.cache; |
| 106 | let uncached_properties = self.uncached_properties.unwrap_or_default(); |
| 107 | |
| 108 | Ok(Proxy { |
| 109 | inner: Arc::new(ProxyInner::new( |
| 110 | conn, |
| 111 | destination, |
| 112 | path, |
| 113 | interface, |
| 114 | cache, |
| 115 | uncached_properties, |
| 116 | )), |
| 117 | }) |
| 118 | } |
| 119 | |
| 120 | /// Build a proxy from the builder. |
| 121 | /// |
| 122 | /// # Errors |
| 123 | /// |
| 124 | /// If the builder is lacking the necessary parameters to build a proxy, |
| 125 | /// [`Error::MissingParameter`] is returned. |
| 126 | pub async fn build(self) -> Result<T> |
| 127 | where |
| 128 | T: From<Proxy<'a>>, |
| 129 | { |
| 130 | let cache_upfront = self.cache == CacheProperties::Yes; |
| 131 | let proxy = self.build_internal()?; |
| 132 | |
| 133 | if cache_upfront { |
| 134 | proxy |
| 135 | .get_property_cache() |
| 136 | .expect("properties cache not initialized" ) |
| 137 | .ready() |
| 138 | .await?; |
| 139 | } |
| 140 | |
| 141 | Ok(proxy.into()) |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | impl<'a, T> Builder<'a, T> |
| 146 | where |
| 147 | T: ProxyDefault, |
| 148 | { |
| 149 | /// Create a new [`Builder`] for the given connection. |
| 150 | #[must_use ] |
| 151 | pub fn new(conn: &Connection) -> Self { |
| 152 | Self { |
| 153 | conn: conn.clone(), |
| 154 | destination: T::DESTINATION |
| 155 | .map(|d| BusName::from_static_str(d).expect("invalid bus name" )), |
| 156 | path: T::PATH.map(|p| ObjectPath::from_static_str(p).expect("invalid default path" )), |
| 157 | interface: T::INTERFACE |
| 158 | .map(|i| InterfaceName::from_static_str(i).expect("invalid interface name" )), |
| 159 | cache: CacheProperties::default(), |
| 160 | uncached_properties: None, |
| 161 | proxy_type: PhantomData, |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | /// Create a new [`Builder`] for the given connection. |
| 166 | #[must_use ] |
| 167 | #[deprecated ( |
| 168 | since = "4.0.0" , |
| 169 | note = "use `Builder::new` instead, which is now generic over the proxy type" |
| 170 | )] |
| 171 | pub fn new_bare(conn: &Connection) -> Self { |
| 172 | Self::new(conn) |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | /// Trait for the default associated values of a proxy. |
| 177 | /// |
| 178 | /// The trait is automatically implemented by the [`dbus_proxy`] macro on your behalf, and may be |
| 179 | /// later used to retrieve the associated constants. |
| 180 | /// |
| 181 | /// [`dbus_proxy`]: attr.dbus_proxy.html |
| 182 | pub trait ProxyDefault { |
| 183 | const INTERFACE: Option<&'static str>; |
| 184 | const DESTINATION: Option<&'static str>; |
| 185 | const PATH: Option<&'static str>; |
| 186 | } |
| 187 | |
| 188 | impl ProxyDefault for Proxy<'_> { |
| 189 | const INTERFACE: Option<&'static str> = None; |
| 190 | const DESTINATION: Option<&'static str> = None; |
| 191 | const PATH: Option<&'static str> = None; |
| 192 | } |
| 193 | |
| 194 | #[cfg (test)] |
| 195 | mod tests { |
| 196 | use super::*; |
| 197 | use test_log::test; |
| 198 | |
| 199 | #[test ] |
| 200 | #[ntest::timeout(15000)] |
| 201 | fn builder() { |
| 202 | crate::utils::block_on(builder_async()); |
| 203 | } |
| 204 | |
| 205 | async fn builder_async() { |
| 206 | let conn = Connection::session().await.unwrap(); |
| 207 | |
| 208 | let builder = Builder::<Proxy<'_>>::new(&conn) |
| 209 | .destination("org.freedesktop.DBus" ) |
| 210 | .unwrap() |
| 211 | .path("/some/path" ) |
| 212 | .unwrap() |
| 213 | .interface("org.freedesktop.Interface" ) |
| 214 | .unwrap() |
| 215 | .cache_properties(CacheProperties::No); |
| 216 | assert!(matches!( |
| 217 | builder.clone().destination.unwrap(), |
| 218 | BusName::Unique(_), |
| 219 | )); |
| 220 | let proxy = builder.build().await.unwrap(); |
| 221 | assert!(matches!(proxy.inner.destination, BusName::Unique(_))); |
| 222 | } |
| 223 | } |
| 224 | |