use std::collections::{BTreeMap, HashMap};
use std::any::{TypeId, Any};
use std::ffi::{CString, CStr};
use std::fmt;
use std::ops::Deref;
use dbus::strings::{Path as PathName, Interface as IfaceName, Member as MemberName, Signature};
use dbus::{Message, MessageType, channel};
use dbus::message::MatchRule;
use super::info::{IfaceInfo, MethodInfo, PropInfo, IfaceInfoBuilder, EmitsChangedSignal};
use super::handlers::{self, Handlers, Par};
use super::stdimpl::{DBusProperties, DBusIntrospectable, DBusObjectManager};
use super::path::{Path, PathData};
use super::context::{MsgCtx, RefCtx};
use super::MethodErr;
pub (super) struct RegEntry<H: Handlers> {
pub typeid: TypeId,
pub info: IfaceInfo<'static, H>,
pub path_insert: Option<Box<dyn Fn(&mut Path<H>, &Crossroads<H>) + Send + Sync>>
}
impl<H: Handlers> RegEntry<H> {
pub fn new<I: 'static>(name: IfaceName<'static>) -> Self {
RegEntry {
typeid: TypeId::of::<I>(),
info: IfaceInfo::new_empty(name),
path_insert: None
}
}
}
impl<H: Handlers> fmt::Debug for RegEntry<H> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "RegEntry") }
}
#[derive(Debug)]
pub struct Crossroads<H: Handlers> {
pub (super) reg: BTreeMap<CString, RegEntry<H>>,
pub (super) paths: BTreeMap<CString, Path<H>>,
}
impl<H: Handlers> Crossroads<H> {
pub fn insert(&mut self, mut path: Path<H>) {
for x in self.reg.values() {
if let Some(ref cb) = x.path_insert { cb(&mut path, self) }
}
let c = path.name().clone().into_cstring();
self.paths.insert(c, path);
}
pub fn get<N: Into<PathName<'static>>>(&self, name: N) -> Option<&Path<H>> {
self.paths.get(name.into().as_cstr())
}
pub fn get_mut<N: Into<PathName<'static>>>(&mut self, name: N) -> Option<&mut Path<H>> {
self.paths.get_mut(name.into().as_cstr())
}
pub fn register<'a, I: 'static, N: Into<IfaceName<'static>>>(&'a mut self, name: N) -> IfaceInfoBuilder<'a, I, H> {
IfaceInfoBuilder::new(Some(self), name.into())
}
pub (super) fn prop_lookup_mut<'a>(&'a mut self, path: &CStr, iname: &CStr, propname: &str) ->
Option<(&'a mut PropInfo<'static, H>, &'a mut Path<H>, EmitsChangedSignal)> {
use std::convert::TryInto;
let entry = self.reg.get_mut(iname)?;
let emits = (&entry.info.anns).try_into();
let propinfo = entry.info.props.iter_mut().find(|x| &x.name == propname)?;
let emits = (&propinfo.anns).try_into().or(emits);
let path = self.paths.get_mut(path)?;
Some((propinfo, path, emits.unwrap_or(EmitsChangedSignal::True)))
}
fn new_noprops(reg_default: bool) -> Self where DBusIntrospectable: PathData<H::Iface> {
let mut cr = Crossroads {
reg: BTreeMap::new(),
paths: BTreeMap::new(),
};
if reg_default {
DBusIntrospectable::register(&mut cr);
cr.insert(Path::new("/"));
}
cr
}
pub (super) fn dispatch_ref(&self, ctx: &mut MsgCtx) -> Result<Option<Message>, MethodErr> {
let refctx = RefCtx::new(self, ctx)?;
let entry = self.reg.get(ctx.interface().as_cstr()).ok_or_else(|| { MethodErr::no_interface(ctx.interface()) })?;
let minfo = entry.info.methods.iter().find(|x| x.name() == ctx.member())
.ok_or_else(|| { MethodErr::no_interface(ctx.member()) })?;
Ok(H::call_method_ref(&minfo.handler(), ctx, &refctx))
}
fn post_dispatch<C: channel::Sender>(&self, ctx: MsgCtx, reply: Result<Option<Message>, MethodErr>, c: &C) {
let r = reply.unwrap_or_else(|e| Some(e.to_message(&ctx.message())));
if let Some(reply) = r {
let _ = c.send(reply);
}
for retmsg in ctx.send_extra.into_iter() { let _ = c.send(retmsg); }
for sigmsg in ctx.signals.into_messages() { let _ = c.send(sigmsg); }
}
pub fn dispatch<C: channel::Sender>(&mut self, msg: Message, c: &C) -> Result<(), ()> {
let mut ctx = MsgCtx::new(msg).ok_or(())?;
let r = H::call_method_mut(self, &mut ctx);
self.post_dispatch(ctx, r, c);
Ok(())
}
}
impl Crossroads<()> {
pub fn new(reg_default: bool) -> Self {
let mut cr = Self::new_noprops(reg_default);
if reg_default {
DBusProperties::register(&mut cr);
DBusObjectManager::register(&mut cr);
}
cr
}
pub fn start<C>(mut self, connection: &C) -> channel::Token
where
C: channel::MatchingReceiver<F=Box<dyn FnMut(Message, &C) -> bool + Send>> + channel::Sender
{
let mut mr = MatchRule::new();
mr.msg_type = Some(MessageType::MethodCall);
connection.start_receive(mr, Box::new(move |msg, c| {
let _ = self.dispatch(msg, c);
true
}))
}
}
impl Crossroads<Par> {
pub fn dispatch_par<C: channel::Sender>(&self, msg: Message, c: &C) -> Result<(), ()> {
let mut ctx = MsgCtx::new(msg).ok_or(())?;
let r = self.dispatch_ref(&mut ctx);
self.post_dispatch(ctx, r, c);
Ok(())
}
pub fn new_par(reg_default: bool) -> Self {
let mut cr = Self::new_noprops(reg_default);
if reg_default { DBusProperties::register_par(&mut cr); }
cr
}
pub fn start_par<C, CC, CR>(cr: CR, connection: CC) -> channel::Token
where
C: channel::MatchingReceiver<F=Box<dyn FnMut(Message, &C) -> bool + Send + Sync>> + channel::Sender,
CC: Deref<Target=C>,
CR: Deref<Target=Self> + Send + Sync + 'static,
{
let mut mr = MatchRule::new();
mr.msg_type = Some(MessageType::MethodCall);
connection.start_receive(mr, Box::new(move |msg, c| {
let _ = cr.dispatch_par(msg, c);
true
}))
}
}
impl Crossroads<handlers::Local> {
pub fn new_local(reg_default: bool) -> Self {
let mut cr = Self::new_noprops(reg_default);
if reg_default {
DBusProperties::register_local(&mut cr);
DBusObjectManager::register_local(&mut cr);
}
cr
}
pub fn start_local<C>(mut self, connection: &C) -> channel::Token
where
C: channel::MatchingReceiver<F=Box<dyn FnMut(Message, &C) -> bool>> + channel::Sender
{
let mut mr = MatchRule::new();
mr.msg_type = Some(MessageType::MethodCall);
connection.start_receive(mr, Box::new(move |msg, c| {
let _ = self.dispatch(msg, c);
true
}))
}
}
#[cfg(test)]
mod test {
use super::*;
use std::cell::RefCell;
#[test]
fn test_send_sync() {
fn is_send<T: Send>(_: &T) {}
fn is_sync<T: Sync>(_: &T) {}
let c = Crossroads::new_par(true);
dbg!(&c);
is_send(&c);
is_sync(&c);
let c2 = Crossroads::new(true);
is_send(&c2);
}
fn dispatch_helper2<H: Handlers>(cr: &mut Crossroads<H>, mut msg: Message) -> Vec<Message> {
msg.set_serial(57);
let r = RefCell::new(vec!());
cr.dispatch(msg, &r).unwrap();
r.into_inner()
}
fn dispatch_helper<H: Handlers>(cr: &mut Crossroads<H>, msg: Message) -> Message {
let mut r = dispatch_helper2(cr, msg);
assert_eq!(r.len(), 1);
r[0].as_result().unwrap();
r.into_iter().next().unwrap()
}
#[test]
fn object_manager() {
let mut cr = Crossroads::new(true);
let istr = "com.example.dbusrs.crossroads.score";
use dbus::arg;
use dbus::arg::Variant;
struct Score(u16);
cr.register::<Score,_>(istr)
.prop_rw("Score", |score: &Score, _: &mut MsgCtx| { Ok(score.0) }, |score: &mut Score, _: &mut MsgCtx, new_val: &u16| {
score.0 = *new_val;
dbg!(new_val);
Ok(Some(*new_val))
})
.prop_ro("Whatever", |score: &Score, _: &mut MsgCtx| { Ok("lorem ipsum") });
cr.insert(Path::new("/hello").with(Score(7u16)).with(DBusObjectManager));
cr.insert(Path::new("/hellothere").with(Score(3u16)));
cr.insert(Path::new("/hello/world").with(Score(5u16)));
let msg = Message::new_method_call("com.example.dbusrs.crossroads.score", "/hello", "org.freedesktop.DBus.Properties", "Set").unwrap();
let msg = msg.append3("com.example.dbusrs.crossroads.score", "Score", Variant(8u16));
let v = dispatch_helper2(&mut cr, msg);
dbg!(&v);
assert_eq!(v.len(), 2);
use dbus::blocking::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged as PPC;
let ppc: PPC = dbus::message::SignalArgs::from_message(&v[1]).unwrap();
let cp = ppc.changed_properties.get("Score").unwrap();
assert_eq!(cp.0.as_i64(), Some(8));
let msg = Message::new_method_call("com.example.dbusrs.crossroads.score", "/hello", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects").unwrap();
let r = dispatch_helper(&mut cr, msg);
let d: HashMap<dbus::strings::Path, HashMap<String, HashMap<String, Variant<Box<dyn arg::RefArg>>>>> = r.read1().unwrap();
dbg!(&d);
assert_eq!(d.get(&"/hello".into()).unwrap().get(istr).unwrap().get("Score").unwrap().0.as_i64().unwrap(), 8);
assert_eq!(d.get(&"/hello/world".into()).unwrap().get(istr).unwrap().get("Score").unwrap().0.as_i64().unwrap(), 5);
assert_eq!(d.get(&"/hello/world".into()).unwrap().get(istr).unwrap().get("Whatever").unwrap().0.as_str().unwrap(), "lorem ipsum");
assert!(d.get(&"/hellothere".into()).is_none());
}
#[test]
fn cr_local() {
let mut cr = Crossroads::new_local(true);
struct Score(u16);
let mut call_times = 0u32;
cr.register::<Score,_>("com.example.dbusrs.crossroads.score")
.annotate("com.example.dbusrs.whatever", "Funny annotation")
.method("UpdateScore", ("change",), ("new_score", "call_times"), move |_: &mut MsgCtx, score: &mut Score, (change,): (u16,)| {
score.0 += change;
call_times += 1;
Ok((score.0, call_times))
}).deprecated();
let mut pdata = Path::new("/");
pdata.insert(Score(7u16));
cr.insert(pdata);
let msg = Message::new_method_call("com.example.dbusrs.crossroads.score", "/", "com.example.dbusrs.crossroads.score", "UpdateScore").unwrap();
let r = dispatch_helper(&mut cr, msg.append1(5u16));
let (new_score, call_times): (u16, u32) = r.read2().unwrap();
assert_eq!(new_score, 12);
assert_eq!(call_times, 1);
let msg = Message::new_method_call("com.example.dbusrs.crossroads.score", "/", "org.freedesktop.DBus.Introspectable", "Introspect").unwrap();
let r = dispatch_helper(&mut cr, msg);
let xml_data: &str = r.read1().unwrap();
println!("{}", xml_data);
}
#[test]
fn cr_par() {
fn dispatch_helper(cr: &Crossroads<Par>, mut msg: Message) -> Message {
msg.set_serial(57);
let r = RefCell::new(vec!());
cr.dispatch_par(msg, &r).unwrap();
let mut r = r.into_inner();
assert_eq!(r.len(), 1);
r[0].as_result().unwrap();
r.into_iter().next().unwrap()
}
let mut cr = Crossroads::new_par(true);
use std::sync::Mutex;
use dbus::arg;
use dbus::arg::Variant;
struct Score(u16, Mutex<u32>);
cr.register::<Score,_>("com.example.dbusrs.crossroads.score")
.method("Hello", ("sender",), ("reply",), |score: &Score, _: &mut MsgCtx, _: &RefCtx<_>, (sender,): (String,)| {
assert_eq!(score.0, 7u16);
Ok((format!("Hello {}, my score is {}!", sender, score.0),))
})
.prop_ro("Score", |score: &Score| {
assert_eq!(score.0, 7u16);
Ok(score.0)
}).emits_changed(super::super::info::EmitsChangedSignal::False)
.prop_rw("Dummy",
|score: &Score, _: &mut MsgCtx, _: &RefCtx<_>| { Ok(*score.1.lock().unwrap()) },
|score: &Score, val: &u32, _: &mut MsgCtx, _: &RefCtx<_>| { *score.1.lock().unwrap() = *val; Ok(false) })
.signal::<(u16,),_>("ScoreChanged", ("NewScore",));
let mut pdata = Path::new("/");
pdata.insert(Score(7u16, Mutex::new(37u32)));
cr.insert(pdata);
let msg = Message::new_method_call("com.example.dbusrs.crossroads.score", "/", "com.example.dbusrs.crossroads.score", "Hello").unwrap();
let msg = msg.append1("example");
let r = dispatch_helper(&cr, msg);
let rr: String = r.read1().unwrap();
assert_eq!(&rr, "Hello example, my score is 7!");
let msg = Message::new_method_call("com.example.dbusrs.crossroads.score", "/", "org.freedesktop.DBus.Properties", "Get").unwrap();
let msg = msg.append2("com.example.dbusrs.crossroads.score", "Score");
let r = dispatch_helper(&cr, msg);
let z: Variant<u16> = r.read1().unwrap();
assert_eq!(z.0, 7u16);
let msg = Message::new_method_call("com.example.dbusrs.crossroads.score", "/", "org.freedesktop.DBus.Properties", "Set").unwrap();
let msg = msg.append3("com.example.dbusrs.crossroads.score", "Dummy", Variant(987u32));
let r = dispatch_helper(&cr, msg);
let msg = Message::new_method_call("com.example.dbusrs.crossroads.score", "/", "org.freedesktop.DBus.Properties", "GetAll").unwrap();
let msg = msg.append1("com.example.dbusrs.crossroads.score");
let r = dispatch_helper(&cr, msg);
let z: HashMap<String, Variant<Box<dyn arg::RefArg>>> = r.read1().unwrap();
println!("{:?}", z);
assert_eq!(z.get("Dummy").unwrap().0.as_i64().unwrap(), 987);
let msg = Message::new_method_call("com.example.dbusrs.crossroads.score", "/", "org.freedesktop.DBus.Introspectable", "Introspect").unwrap();
let r = dispatch_helper(&cr, msg);
let xml_data: &str = r.read1().unwrap();
println!("{}", xml_data);
}
}