From 149f77c5652ce769c9a094305c398eede9a48904 Mon Sep 17 00:00:00 2001 From: Sebastian Wiesner Date: Thu, 31 Oct 2024 07:54:34 +0100 Subject: [PATCH] Add DBusMethodCall convenience trait This trait represents a parsed method call with deserialized arguments, to abstract over call parsing. Then add new registration builder helpers to register method calls with a simplified callback which receives parsed arguments, and can optionally return an async result. --- examples/gio_dbus_register_object/main.rs | 30 +++--- gio/src/dbus_connection.rs | 119 +++++++++++++++++++++- gio/src/prelude.rs | 9 +- 3 files changed, 138 insertions(+), 20 deletions(-) diff --git a/examples/gio_dbus_register_object/main.rs b/examples/gio_dbus_register_object/main.rs index 37a4143e8453..56b5e16397ae 100644 --- a/examples/gio_dbus_register_object/main.rs +++ b/examples/gio_dbus_register_object/main.rs @@ -32,16 +32,21 @@ struct SlowHello { } #[derive(Debug)] -enum Call { +enum HelloMethod { Hello(Hello), SlowHello(SlowHello), } -impl Call { - pub fn parse(method: &str, parameters: glib::Variant) -> Result { +impl DBusMethodCall for HelloMethod { + fn parse_call( + _obj_path: &str, + _interface: &str, + method: &str, + params: glib::Variant, + ) -> Result { match method { - "Hello" => Ok(parameters.get::().map(Call::Hello)), - "SlowHello" => Ok(parameters.get::().map(Call::SlowHello)), + "Hello" => Ok(params.get::().map(Self::Hello)), + "SlowHello" => Ok(params.get::().map(Self::SlowHello)), _ => Err(glib::Error::new(IOErrorEnum::Failed, "No such method")), } .and_then(|p| p.ok_or_else(|| glib::Error::new(IOErrorEnum::Failed, "Invalid parameters"))) @@ -58,23 +63,24 @@ fn on_startup(app: &gio::Application, tx: &Sender) { if let Ok(id) = connection .register_object("/com/github/gtk_rs/examples/HelloWorld", &example) - .method_call(move |_, _, _, _, method, params, invocation| { - let call = Call::parse(method, params); - invocation.return_future_local(async move { - match call? { - Call::Hello(Hello { name }) => { + .parse_method_call::() + .invoke_and_return_future_local(|_, sender, call| { + println!("Method call from {sender}"); + async { + match call { + HelloMethod::Hello(Hello { name }) => { let greet = format!("Hello {name}!"); println!("{greet}"); Ok(Some(greet.to_variant())) } - Call::SlowHello(SlowHello { name, delay }) => { + HelloMethod::SlowHello(SlowHello { name, delay }) => { glib::timeout_future(Duration::from_secs(delay as u64)).await; let greet = format!("Hello {name} after {delay} seconds!"); println!("{greet}"); Ok(Some(greet.to_variant())) } } - }); + } }) .build() { diff --git a/gio/src/dbus_connection.rs b/gio/src/dbus_connection.rs index 982c2f8a164c..22180bb0cc09 100644 --- a/gio/src/dbus_connection.rs +++ b/gio/src/dbus_connection.rs @@ -1,13 +1,110 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use std::{boxed::Box as Box_, num::NonZeroU32}; - -use glib::{prelude::*, translate::*}; +use std::{boxed::Box as Box_, future::Future, marker::PhantomData, num::NonZeroU32}; use crate::{ ffi, ActionGroup, DBusConnection, DBusInterfaceInfo, DBusMessage, DBusMethodInvocation, DBusSignalFlags, MenuModel, }; +use glib::{prelude::*, translate::*}; + +pub trait DBusMethodCall: Sized { + fn parse_call( + obj_path: &str, + interface: &str, + method: &str, + params: glib::Variant, + ) -> Result; +} + +// rustdoc-stripper-ignore-next +/// Handle method invocations. +pub struct MethodCallBuilder<'a, T> { + registration: RegistrationBuilder<'a>, + capture_type: PhantomData, +} + +impl<'a, T: DBusMethodCall> MethodCallBuilder<'a, T> { + // rustdoc-stripper-ignore-next + /// Handle invocation of a parsed method call. + /// + /// For each DBus method call parse the call, and then invoke the given closure + /// with + /// + /// 1. the DBus connection object, + /// 2. the name of the sender of the method call, + /// 3. the parsed call, and + /// 4. the method invocation object. + /// + /// The closure **must** return a value through the invocation object in all + /// code paths, using any of its `return_` functions, such as + /// [`DBusMethodInvocation::return_result`] or + /// [`DBusMethodInvocation::return_future_local`], to finish the call. + /// + /// If direct access to the invocation object is not needed, + /// [`invoke_and_return`] and [`invoke_and_return_future_local`] provide a + /// safer interface where the callback returns a result directly. + pub fn invoke(self, f: F) -> RegistrationBuilder<'a> + where + F: Fn(DBusConnection, &str, T, DBusMethodInvocation) + 'static, + { + self.registration.method_call( + move |connection, sender, obj_path, interface, method, params, invocation| { + match T::parse_call(obj_path, interface, method, params) { + Ok(call) => f(connection, sender, call, invocation), + Err(error) => invocation.return_gerror(error), + } + }, + ) + } + + // rustdoc-stripper-ignore-next + /// Handle invocation of a parsed method call. + /// + /// For each DBus method call parse the call, and then invoke the given closure + /// with + /// + /// 1. the DBus connection object, + /// 2. the name of the sender of the method call, and + /// 3. the parsed call. + /// + /// The return value of the closure is then returned on the method call. + /// If the returned variant value is not a tuple, it is automatically wrapped + /// in a single element tuple, as DBus methods must always return tuples. + /// See [`DBusMethodInvocation::return_result`] for details. + pub fn invoke_and_return(self, f: F) -> RegistrationBuilder<'a> + where + F: Fn(DBusConnection, &str, T) -> Result, glib::Error> + 'static, + { + self.invoke(move |connection, sender, call, invocation| { + invocation.return_result(f(connection, sender, call)) + }) + } + + // rustdoc-stripper-ignore-next + /// Handle an async invocation of a parsed method call. + /// + /// For each DBus method call parse the call, and then invoke the given closure + /// with + /// + /// 1. the DBus connection object, + /// 2. the name of the sender of the method call, and + /// 3. the parsed call. + /// + /// The output of the future is then returned on the method call. + /// If the returned variant value is not a tuple, it is automatically wrapped + /// in a single element tuple, as DBus methods must always return tuples. + /// See [`DBusMethodInvocation::return_future_local`] for details. + pub fn invoke_and_return_future_local(self, f: F) -> RegistrationBuilder<'a> + where + F: Fn(DBusConnection, &str, T) -> Fut + 'static, + Fut: Future, glib::Error>> + 'static, + { + self.invoke(move |connection, sender, call, invocation| { + invocation.return_future_local(f(connection, sender, call)); + }) + } +} #[derive(Debug, Eq, PartialEq)] pub struct RegistrationId(NonZeroU32); @@ -22,6 +119,8 @@ pub struct FilterId(NonZeroU32); #[derive(Debug, Eq, PartialEq)] pub struct SignalSubscriptionId(NonZeroU32); +// rustdoc-stripper-ignore-next +/// Build a registered DBus object, by handling different parts of DBus. #[must_use = "The builder must be built to be used"] pub struct RegistrationBuilder<'a> { connection: &'a DBusConnection, @@ -38,7 +137,7 @@ pub struct RegistrationBuilder<'a> { Option bool>>, } -impl RegistrationBuilder<'_> { +impl<'a> RegistrationBuilder<'a> { pub fn method_call< F: Fn(DBusConnection, &str, &str, &str, &str, glib::Variant, DBusMethodInvocation) + 'static, >( @@ -49,6 +148,18 @@ impl RegistrationBuilder<'_> { self } + // rustdoc-stripper-ignore-next + /// Parse method calls to this object. + /// + /// Deserialize the method name and the method parameters, and then handle + /// method invocations. + pub fn parse_method_call(self) -> MethodCallBuilder<'a, T> { + MethodCallBuilder { + registration: self, + capture_type: Default::default(), + } + } + #[doc(alias = "get_property")] pub fn property glib::Variant + 'static>( mut self, diff --git a/gio/src/prelude.rs b/gio/src/prelude.rs index 9c9e1998c57c..05bef53b4480 100644 --- a/gio/src/prelude.rs +++ b/gio/src/prelude.rs @@ -35,10 +35,11 @@ pub use crate::{ action_map::ActionMapExtManual, application::ApplicationExtManual, auto::traits::*, cancellable::CancellableExtManual, converter::ConverterExtManual, data_input_stream::DataInputStreamExtManual, datagram_based::DatagramBasedExtManual, - dbus_proxy::DBusProxyExtManual, file::FileExtManual, file_enumerator::FileEnumeratorExtManual, - inet_address::InetAddressExtManual, input_stream::InputStreamExtManual, - io_stream::IOStreamExtManual, list_model::ListModelExtManual, - output_stream::OutputStreamExtManual, pollable_input_stream::PollableInputStreamExtManual, + dbus_connection::DBusMethodCall, dbus_proxy::DBusProxyExtManual, file::FileExtManual, + file_enumerator::FileEnumeratorExtManual, inet_address::InetAddressExtManual, + input_stream::InputStreamExtManual, io_stream::IOStreamExtManual, + list_model::ListModelExtManual, output_stream::OutputStreamExtManual, + pollable_input_stream::PollableInputStreamExtManual, pollable_output_stream::PollableOutputStreamExtManual, settings::SettingsExtManual, simple_proxy_resolver::SimpleProxyResolverExtManual, socket::SocketExtManual, socket_control_message::SocketControlMessageExtManual,