1use std::{collections::HashSet, convert::TryInto, marker::PhantomData, sync::Arc};
2
3use static_assertions::assert_impl_all;
4use zbus_names::{BusName, InterfaceName};
5use zvariant::{ObjectPath, Str};
6
7use crate::{Connection, Error, Proxy, ProxyInner, Result};
8
9/// The properties caching mode.
10#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
11#[non_exhaustive]
12pub 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)]
25pub 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
35impl<'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
49assert_impl_all!(ProxyBuilder<'_>: Send, Sync, Unpin);
50
51impl<'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
67impl<'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
161impl<'a, T> ProxyBuilder<'a, T>
162where
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
188pub trait ProxyDefault {
189 const INTERFACE: &'static str;
190 const DESTINATION: &'static str;
191 const PATH: &'static str;
192}
193
194#[cfg(test)]
195mod 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