1 | use std::{collections::HashSet, convert::TryInto, 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::{Connection, Error, Proxy, ProxyInner, 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 ProxyBuilder<'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 ProxyBuilder<'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!(ProxyBuilder<'_>: Send, Sync, Unpin); |
50 | |
51 | impl<'a, T> ProxyBuilder<'a, T> { |
52 | /// Create a new [`ProxyBuilder`] for the given connection. |
53 | #[must_use ] |
54 | pub fn new_bare(conn: &Connection) -> Self { |
55 | Self { |
56 | conn: conn.clone(), |
57 | destination: None, |
58 | path: None, |
59 | interface: None, |
60 | cache: CacheProperties::default(), |
61 | uncached_properties: None, |
62 | proxy_type: PhantomData, |
63 | } |
64 | } |
65 | } |
66 | |
67 | impl<'a, T> ProxyBuilder<'a, T> { |
68 | /// Set the proxy destination address. |
69 | pub fn destination<D>(mut self, destination: D) -> Result<Self> |
70 | where |
71 | D: TryInto<BusName<'a>>, |
72 | D::Error: Into<Error>, |
73 | { |
74 | self.destination = Some(destination.try_into().map_err(Into::into)?); |
75 | Ok(self) |
76 | } |
77 | |
78 | /// Set the proxy path. |
79 | pub fn path<P>(mut self, path: P) -> Result<Self> |
80 | where |
81 | P: TryInto<ObjectPath<'a>>, |
82 | P::Error: Into<Error>, |
83 | { |
84 | self.path = Some(path.try_into().map_err(Into::into)?); |
85 | Ok(self) |
86 | } |
87 | |
88 | /// Set the proxy interface. |
89 | pub fn interface<I>(mut self, interface: I) -> Result<Self> |
90 | where |
91 | I: TryInto<InterfaceName<'a>>, |
92 | I::Error: Into<Error>, |
93 | { |
94 | self.interface = Some(interface.try_into().map_err(Into::into)?); |
95 | Ok(self) |
96 | } |
97 | |
98 | /// Set the properties caching mode. |
99 | #[must_use ] |
100 | pub fn cache_properties(mut self, cache: CacheProperties) -> Self { |
101 | self.cache = cache; |
102 | self |
103 | } |
104 | |
105 | /// Specify a set of properties (by name) which should be excluded from caching. |
106 | #[must_use ] |
107 | pub fn uncached_properties(mut self, properties: &[&'a str]) -> Self { |
108 | self.uncached_properties |
109 | .replace(properties.iter().map(|p| Str::from(*p)).collect()); |
110 | |
111 | self |
112 | } |
113 | |
114 | pub(crate) fn build_internal(self) -> Result<Proxy<'a>> { |
115 | let conn = self.conn; |
116 | let destination = self |
117 | .destination |
118 | .ok_or(Error::MissingParameter("destination" ))?; |
119 | let path = self.path.ok_or(Error::MissingParameter("path" ))?; |
120 | let interface = self.interface.ok_or(Error::MissingParameter("interface" ))?; |
121 | let cache = self.cache; |
122 | let uncached_properties = self.uncached_properties.unwrap_or_default(); |
123 | |
124 | Ok(Proxy { |
125 | inner: Arc::new(ProxyInner::new( |
126 | conn, |
127 | destination, |
128 | path, |
129 | interface, |
130 | cache, |
131 | uncached_properties, |
132 | )), |
133 | }) |
134 | } |
135 | |
136 | /// Build a proxy from the builder. |
137 | /// |
138 | /// # Errors |
139 | /// |
140 | /// If the builder is lacking the necessary parameters to build a proxy, |
141 | /// [`Error::MissingParameter`] is returned. |
142 | pub async fn build(self) -> Result<T> |
143 | where |
144 | T: From<Proxy<'a>>, |
145 | { |
146 | let cache_upfront = self.cache == CacheProperties::Yes; |
147 | let proxy = self.build_internal()?; |
148 | |
149 | if cache_upfront { |
150 | proxy |
151 | .get_property_cache() |
152 | .expect("properties cache not initialized" ) |
153 | .ready() |
154 | .await?; |
155 | } |
156 | |
157 | Ok(proxy.into()) |
158 | } |
159 | } |
160 | |
161 | impl<'a, T> ProxyBuilder<'a, T> |
162 | where |
163 | T: ProxyDefault, |
164 | { |
165 | /// Create a new [`ProxyBuilder`] for the given connection. |
166 | #[must_use ] |
167 | pub fn new(conn: &Connection) -> Self { |
168 | Self { |
169 | conn: conn.clone(), |
170 | destination: Some(BusName::from_static_str(T::DESTINATION).expect(msg:"invalid bus name" )), |
171 | path: Some(ObjectPath::from_static_str(T::PATH).expect(msg:"invalid default path" )), |
172 | interface: Some( |
173 | InterfaceName::from_static_str(T::INTERFACE).expect(msg:"invalid interface name" ), |
174 | ), |
175 | cache: CacheProperties::default(), |
176 | uncached_properties: None, |
177 | proxy_type: PhantomData, |
178 | } |
179 | } |
180 | } |
181 | |
182 | /// Trait for the default associated values of a proxy. |
183 | /// |
184 | /// The trait is automatically implemented by the [`dbus_proxy`] macro on your behalf, and may be |
185 | /// later used to retrieve the associated constants. |
186 | /// |
187 | /// [`dbus_proxy`]: attr.dbus_proxy.html |
188 | pub trait ProxyDefault { |
189 | const INTERFACE: &'static str; |
190 | const DESTINATION: &'static str; |
191 | const PATH: &'static str; |
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 = ProxyBuilder::<Proxy<'_>>::new_bare(&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 | |