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 | |