dbus_crossroads/
crossroads.rs

1use std::pin::Pin;
2use std::sync::Arc;
3use dbus::channel::{Sender, default_reply};
4use std::future::Future;
5use std::marker::PhantomData;
6use crate::{Context, MethodErr, IfaceBuilder, stdimpl};
7use crate::ifacedesc::Registry;
8use std::collections::{BTreeMap, HashSet};
9use std::any::Any;
10use std::fmt;
11use crate::utils::Dbg;
12
13const INTROSPECTABLE: usize = 0;
14const PROPERTIES: usize = 1;
15const OBJECT_MANAGER: usize = 2;
16
17/// Contains a reference to a registered interface.
18pub struct IfaceToken<T: Send + 'static>(usize, PhantomData<&'static T>);
19
20impl<T: Send + 'static> Clone for IfaceToken<T> {
21    fn clone(&self) -> Self { IfaceToken(self.0, PhantomData) }
22}
23impl<T: Send + 'static> Copy for IfaceToken<T> {}
24impl<T: Send + 'static> Eq for IfaceToken<T> {}
25impl<T: Send + 'static> PartialEq for IfaceToken<T> {
26    fn eq(&self, a: &Self) -> bool { self.0 == a.0 }
27}
28impl<T: Send + 'static> Ord for IfaceToken<T> {
29    fn cmp(&self, a: &Self) -> std::cmp::Ordering { self.0.cmp(&a.0) }
30}
31impl<T: Send + 'static> PartialOrd for IfaceToken<T> {
32    fn partial_cmp(&self, a: &Self) -> Option<std::cmp::Ordering> { Some(self.0.cmp(&a.0)) }
33}
34impl<T: Send + 'static> fmt::Debug for IfaceToken<T> {
35    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36        write!(f, "IfaceToken({})", self.0)
37    }
38}
39
40
41#[derive(Debug)]
42struct Object {
43    ifaces: HashSet<usize>,
44    data: Box<dyn Any + Send + 'static>
45}
46
47pub type BoxedSpawn = Box<dyn Fn(Pin<Box<dyn Future<Output = ()> + Send + 'static>>) + Send + 'static>;
48
49struct AsyncSupport {
50    sender: Arc<dyn Sender + Send + Sync + 'static>,
51    spawner: BoxedSpawn,
52}
53
54impl fmt::Debug for AsyncSupport {
55    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "AsyncSupport") }
56}
57
58/// Crossroads is the "main" object, containing object paths, a registry of interfaces, and
59/// a crossreference of which object paths implement which interfaces.
60///
61/// You can store some arbitrary data with every object path if you like. This data can then be
62/// accessed from within the method callbacks. If you do not want this, just pass `()` as your data.
63///
64/// Crossroads can contain callbacks and data which is Send, but Sync is not required. Hence
65/// Crossroads itself is Send but not Sync.
66#[derive(Debug)]
67pub struct Crossroads {
68    map: BTreeMap<dbus::Path<'static>, Object>,
69    registry: Registry,
70    add_standard_ifaces: bool,
71    async_support: Option<AsyncSupport>,
72    object_manager_support: Option<Dbg<Arc<dyn Sender + Send + Sync + 'static>>>,
73}
74
75impl Crossroads {
76    /// Create a new Crossroads instance.
77    pub fn new() -> Crossroads {
78        let mut cr = Crossroads {
79            map: Default::default(),
80            registry: Default::default(),
81            add_standard_ifaces: true,
82            async_support: None,
83            object_manager_support: None,
84        };
85        let t0 = stdimpl::introspectable(&mut cr);
86        let t1 = stdimpl::properties(&mut cr);
87        let t2 = stdimpl::object_manager(&mut cr);
88        debug_assert_eq!(t0.0, INTROSPECTABLE);
89        debug_assert_eq!(t1.0, PROPERTIES);
90        debug_assert_eq!(t2.0, OBJECT_MANAGER);
91
92        // Add the root path and make it introspectable. This helps D-Bus debug tools
93        cr.insert("/", &[], ());
94        cr
95    }
96
97    /// If set to true (the default), will make paths implement the standard "Introspectable" and,
98    /// if the path has interfaces with properties, the "Properties" interfaces.
99    pub fn set_add_standard_ifaces(&mut self, enable: bool) {
100        self.add_standard_ifaces = enable;
101    }
102
103    /// Registers a new interface into the interface registry. The closure receives an
104    /// IfaceBuilder that you can add methods, signals and properties to.
105    pub fn register<T, N, F>(&mut self, name: N, f: F) -> IfaceToken<T>
106    where T: Send + 'static, N: Into<dbus::strings::Interface<'static>>,
107    F: FnOnce(&mut IfaceBuilder<T>)
108    {
109        let iface = IfaceBuilder::build(Some(name.into()), f);
110        let x = self.registry.push(iface);
111        IfaceToken(x, PhantomData)
112    }
113
114    /// Access the data of a certain path.
115    ///
116    /// Will return none both if the path was not found, and if the found data was of another type.
117    pub fn data_mut<D: Any + Send + 'static>(&mut self, name: &dbus::Path<'static>) -> Option<&mut D> {
118        let obj = self.map.get_mut(name)?;
119        obj.data.downcast_mut()
120    }
121
122    /// Inserts a new path.
123    ///
124    /// If the path already exists, it is overwritten.
125    pub fn insert<'z, D, I, N>(&mut self, name: N, ifaces: I, data: D)
126    where D: Any + Send + 'static, N: Into<dbus::Path<'static>>, I: IntoIterator<Item = &'z IfaceToken<D>>
127    {
128        let ifaces = ifaces.into_iter().map(|x| x.0);
129        let mut ifaces: HashSet<usize> = std::iter::FromIterator::from_iter(ifaces);
130        if self.add_standard_ifaces {
131            ifaces.insert(INTROSPECTABLE);
132            if ifaces.iter().any(|u| self.registry().has_props(*u)) {
133                ifaces.insert(PROPERTIES);
134            }
135        }
136        let name = name.into();
137        self.map.insert(name.clone(), Object { ifaces, data: Box::new(data)});
138        if let Some(oms) = self.object_manager_support.as_ref() {
139            stdimpl::object_manager_path_added(oms.0.clone(), &name, self);
140        }
141    }
142
143    /// Adds an interface to an existing object path.
144    ///
145    /// Returns true if interface can be added to the object or already exists in the object.
146    /// Returns false if the object does not exist or the interface cannot be added due to data type
147    /// mismatch.
148    pub fn add_interface<'z, D, N>(&mut self, name: N, iface: IfaceToken<D>) -> bool
149    where D: Any + Send + 'static, N: Into<dbus::Path<'static>>
150    {
151        let name = name.into();
152        if let Some(obj) = self.map.get_mut(&name) {
153            if !obj.data.is::<D>() {
154                // Data type mismatch, return fail.
155                return false;
156            }
157
158            if obj.ifaces.contains(&iface.0) {
159                // Already has the interface, return success.
160                return true;
161            }
162
163            obj.ifaces.insert(iface.0);
164            if let Some(oms) = self.object_manager_support.as_ref() {
165                stdimpl::object_manager_interface_added(oms.0.clone(), &name, iface.0, self);
166            }
167
168            return true;
169        }
170        false
171    }
172
173    /// Removes an interface from an object path.
174    pub fn remove_interface<'z, D, N>(&mut self, name: N, iface: IfaceToken<D>)
175    where D: Any + Send + 'static, N: Into<dbus::Path<'static>>
176    {
177        let name = name.into();
178        if let Some(obj) = self.map.get_mut(&name) {
179            if !obj.ifaces.contains(&iface.0) {
180                return;
181            }
182
183            obj.ifaces.remove(&iface.0);
184            if let Some(oms) = self.object_manager_support.as_ref() {
185                stdimpl::object_manager_interface_removed(oms.0.clone(), &name, iface.0, self);
186            }
187        }
188    }
189
190    /// Returns true if the path exists and implements the interface
191    pub fn has_interface<D: Send>(&self, name: &dbus::Path<'static>, token: IfaceToken<D>) -> bool {
192        self.map.get(name).map(|x| x.ifaces.contains(&token.0)).unwrap_or(false)
193    }
194
195    /// Removes an existing path.
196    ///
197    /// Returns None if the path was not found.
198    /// In case of a type mismatch, the path will be removed, but None will be returned.
199    pub fn remove<D>(&mut self, name: &dbus::Path<'static>) -> Option<D>
200    where D: Any + Send + 'static {
201        if let Some(oms) = self.object_manager_support.as_ref() {
202            if self.map.contains_key(name) {
203                stdimpl::object_manager_path_removed(oms.0.clone(), &name, self);
204            }
205        }
206        let x = self.map.remove(name)?;
207        let r: Box<D> = x.data.downcast().ok()?;
208        Some(*r)
209    }
210
211    pub (crate) fn find_iface_token(&self,
212        path: &dbus::Path<'static>,
213        interface: Option<&dbus::strings::Interface<'static>>)
214    -> Result<usize, MethodErr> {
215        let obj = self.map.get(path).ok_or_else(|| MethodErr::no_path(path))?;
216        self.registry.find_token(interface, &obj.ifaces)
217    }
218
219    pub (crate) fn registry(&mut self) -> &mut Registry { &mut self.registry }
220
221    pub (crate) fn registry_and_ifaces(&self, path: &dbus::Path<'static>)
222    -> (&Registry, &HashSet<usize>) {
223        let obj = self.map.get(path).unwrap();
224        (&self.registry, &obj.ifaces)
225    }
226
227    pub (crate) fn get_children(&self, path: &dbus::Path<'static>, direct_only: bool) -> Vec<&str> {
228        use std::ops::Bound;
229        let mut range = self.map.range((Bound::Excluded(path), Bound::Unbounded));
230        let p2 = path.as_bytes();
231        let substart = if &p2 == &b"/" { 0 } else { p2.len() };
232        let mut r: Vec<&str> = vec!();
233        while let Some((c, _)) = range.next() {
234            if !c.as_bytes().starts_with(p2) { break; }
235            let csub: &str = &c[substart..];
236            if csub.len() == 0 || csub.as_bytes()[0] != b'/' { continue; }
237            let csub1 = &csub[1..];
238            if direct_only && r.len() > 0 {
239                let prev = r[r.len()-1].as_bytes();
240                let b = csub1.as_bytes();
241                if b.len() > prev.len() && b.starts_with(prev) && b[prev.len()] == b'/' { continue; }
242            }
243            r.push(csub1);
244        };
245        r
246    }
247
248    pub (crate) fn run_async_method<F, R>(&mut self, f: F)
249    where F: FnOnce(Arc<dyn Sender + Send + Sync + 'static>, &mut Crossroads) -> R,
250    R: Future<Output=()> + Send + 'static
251    {
252        let sender = self.async_support.as_ref().expect("Async support not set").sender.clone();
253        let future = f(sender, self);
254        let spawner = &self.async_support.as_ref().expect("Async support not set").spawner;
255        let boxed = Box::pin(async move { future.await });
256        (spawner)(boxed)
257    }
258
259    fn handle_message_inner(&mut self, mut ctx: Context) -> Option<Context> {
260        let itoken_result = match self.find_iface_token(ctx.path(), ctx.interface()) {
261            Ok(x) => Ok(x),
262            Err(merr) => {
263                if let Some(rmsg) = default_reply(ctx.message()) {
264                    ctx.push_msg(rmsg);
265                    return Some(ctx)
266                }
267                Err(merr)
268            }
269        };
270
271        let (itoken, mut cb) = match ctx.check(|ctx| {
272            let itoken = itoken_result?;
273            let cb = self.registry.take_method(itoken, ctx.method())?;
274            Ok((itoken, cb))
275        }) {
276            Ok(x) => x,
277            Err(_) => return Some(ctx)
278        };
279        // No failure paths before method is given back!
280        let methodname = ctx.method().clone();
281        let ctx = cb(ctx, self);
282        self.registry.give_method(itoken, &methodname, cb);
283        ctx
284    }
285
286    /// Handles an incoming message call.
287    ///
288    /// Returns Err if the message is not a method call.
289    pub fn handle_message<S: dbus::channel::Sender>(&mut self, message: dbus::Message, conn: &S) -> Result<(), ()> {
290        let ctx = Context::new(message).ok_or(())?;
291        if let Some(mut ctx) = self.handle_message_inner(ctx) {
292            let _ = ctx.flush_messages(conn);
293        }
294        Ok(())
295    }
296
297    /// The token representing the built-in implementation of "org.freedesktop.DBus.Introspectable".
298    pub fn introspectable<T: Send + 'static>(&self) -> IfaceToken<T> { IfaceToken(INTROSPECTABLE, PhantomData) }
299
300    /// The token representing the built-in implementation of "org.freedesktop.DBus.Properties".
301    pub fn properties<T: Send + 'static>(&self) -> IfaceToken<T> { IfaceToken(PROPERTIES, PhantomData) }
302
303    /// The token representing the built-in implementation of "org.freedesktop.DBus.ObjectManager".
304    ///
305    /// You can add this to a path without enabling "set_object_manager_support", but no signals will
306    /// be sent.
307    pub fn object_manager<T: Send + 'static>(&self) -> IfaceToken<T> { IfaceToken(OBJECT_MANAGER, PhantomData) }
308
309    /// Enables this crossroads instance to run asynchronous methods (and setting properties).
310    ///
311    /// Incoming method calls are spawned as separate tasks if necessary. This provides the necessary
312    /// abstractions needed to spawn a new tasks, and to send the reply when the task has finished.
313    pub fn set_async_support(&mut self, x: Option<(Arc<dyn Sender + Send + Sync + 'static>, BoxedSpawn)>) -> Option<(Arc<dyn Sender + Send + Sync + 'static>, BoxedSpawn)> {
314        let a = self.async_support.take();
315        self.async_support = x.map(|x| AsyncSupport {
316            sender: x.0,
317            spawner: x.1
318        });
319        a.map(|x| (x.sender, x.spawner))
320    }
321
322    /// Enables this crossroads instance to send signals when paths are added and removed.
323    ///
324    /// The added/removed path is a subpath of a path which implements an object manager instance.
325    pub fn set_object_manager_support(&mut self, x: Option<Arc<dyn Sender + Send + Sync + 'static>>) -> Option<Arc<dyn Sender + Send + Sync + 'static>> {
326        let x = x.map(|x| Dbg(x));
327        std::mem::replace(&mut self.object_manager_support, x).map(|x| x.0)
328    }
329
330    /// Serve clients forever on a blocking Connection.
331    ///
332    /// This is a quick one-liner for the simplest case. In more advanced scenarios, you
333    /// probably have to write similar code yourself.
334    pub fn serve(mut self, connection: &dbus::blocking::Connection) -> Result<(), dbus::Error> {
335        // We add the Crossroads instance to the connection so that incoming method calls will be handled.
336        use dbus::channel::MatchingReceiver;
337        connection.start_receive(dbus::message::MatchRule::new_method_call(), Box::new(move |msg, conn| {
338            self.handle_message(msg, conn).unwrap();
339            true
340        }));
341
342        // Serve clients forever.
343        loop { connection.process(std::time::Duration::from_millis(1000))?; }
344    }
345}