Skip to content

Commit

Permalink
Add DBusMethodCall convenience trait
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
swsnr committed Nov 1, 2024
1 parent 39b18f8 commit 149f77c
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 20 deletions.
30 changes: 18 additions & 12 deletions examples/gio_dbus_register_object/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Call, glib::Error> {
impl DBusMethodCall for HelloMethod {
fn parse_call(
_obj_path: &str,
_interface: &str,
method: &str,
params: glib::Variant,
) -> Result<Self, glib::Error> {
match method {
"Hello" => Ok(parameters.get::<Hello>().map(Call::Hello)),
"SlowHello" => Ok(parameters.get::<SlowHello>().map(Call::SlowHello)),
"Hello" => Ok(params.get::<Hello>().map(Self::Hello)),
"SlowHello" => Ok(params.get::<SlowHello>().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")))
Expand All @@ -58,23 +63,24 @@ fn on_startup(app: &gio::Application, tx: &Sender<gio::RegistrationId>) {

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::<HelloMethod>()
.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()
{
Expand Down
119 changes: 115 additions & 4 deletions gio/src/dbus_connection.rs
Original file line number Diff line number Diff line change
@@ -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<Self, glib::Error>;
}

// rustdoc-stripper-ignore-next
/// Handle method invocations.
pub struct MethodCallBuilder<'a, T> {
registration: RegistrationBuilder<'a>,
capture_type: PhantomData<T>,
}

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<F>(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<F>(self, f: F) -> RegistrationBuilder<'a>
where
F: Fn(DBusConnection, &str, T) -> Result<Option<glib::Variant>, 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<F, Fut>(self, f: F) -> RegistrationBuilder<'a>
where
F: Fn(DBusConnection, &str, T) -> Fut + 'static,
Fut: Future<Output = Result<Option<glib::Variant>, 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);
Expand All @@ -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,
Expand All @@ -38,7 +137,7 @@ pub struct RegistrationBuilder<'a> {
Option<Box_<dyn Fn(DBusConnection, &str, &str, &str, &str, glib::Variant) -> bool>>,
}

impl RegistrationBuilder<'_> {
impl<'a> RegistrationBuilder<'a> {
pub fn method_call<
F: Fn(DBusConnection, &str, &str, &str, &str, glib::Variant, DBusMethodInvocation) + 'static,
>(
Expand All @@ -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<T: DBusMethodCall>(self) -> MethodCallBuilder<'a, T> {
MethodCallBuilder {
registration: self,
capture_type: Default::default(),
}
}

#[doc(alias = "get_property")]
pub fn property<F: Fn(DBusConnection, &str, &str, &str, &str) -> glib::Variant + 'static>(
mut self,
Expand Down
9 changes: 5 additions & 4 deletions gio/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 149f77c

Please sign in to comment.