feat: add support for async property get/set on client-side (#354)

* feat: add async property get/set convenience support classes

* feat: add no-reply and async overloads to Properties_proxy

* feat: add convenience functions for GetAll functionality

* test: add tests for new functionality

* add codegen IDL support and documentation
This commit is contained in:
Stanislav Angelovič
2023-09-14 10:54:57 +02:00
committed by GitHub
parent 0eda855745
commit 290078d6af
9 changed files with 685 additions and 57 deletions
+62 -6
View File
@@ -29,6 +29,7 @@
#include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Flags.h>
#include <string>
#include <vector>
@@ -41,7 +42,6 @@
namespace sdbus {
class IObject;
class IProxy;
class Variant;
class Error;
class PendingAsyncCall;
}
@@ -225,7 +225,7 @@ namespace sdbus {
{
public:
SignalUnsubscriber(IProxy& proxy, const std::string& signalName);
void onInterface(std::string interfaceName);
void onInterface(const std::string& interfaceName);
private:
IProxy& proxy_;
@@ -236,25 +236,81 @@ namespace sdbus {
{
public:
PropertyGetter(IProxy& proxy, const std::string& propertyName);
sdbus::Variant onInterface(const std::string& interfaceName);
Variant onInterface(const std::string& interfaceName);
private:
IProxy& proxy_;
const std::string& propertyName_;
};
class AsyncPropertyGetter
{
public:
AsyncPropertyGetter(IProxy& proxy, const std::string& propertyName);
AsyncPropertyGetter& onInterface(const std::string& interfaceName);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
std::future<Variant> getResultAsFuture();
private:
IProxy& proxy_;
const std::string& propertyName_;
const std::string* interfaceName_{};
};
class PropertySetter
{
public:
PropertySetter(IProxy& proxy, const std::string& propertyName);
PropertySetter& onInterface(std::string interfaceName);
PropertySetter& onInterface(const std::string& interfaceName);
template <typename _Value> void toValue(const _Value& value);
void toValue(const sdbus::Variant& value);
template <typename _Value> void toValue(const _Value& value, dont_expect_reply_t);
void toValue(const Variant& value);
void toValue(const Variant& value, dont_expect_reply_t);
private:
IProxy& proxy_;
const std::string& propertyName_;
std::string interfaceName_;
const std::string* interfaceName_{};
};
class AsyncPropertySetter
{
public:
AsyncPropertySetter(IProxy& proxy, const std::string& propertyName);
AsyncPropertySetter& onInterface(const std::string& interfaceName);
template <typename _Value> AsyncPropertySetter& toValue(_Value&& value);
AsyncPropertySetter& toValue(Variant value);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
std::future<void> getResultAsFuture();
private:
IProxy& proxy_;
const std::string& propertyName_;
const std::string* interfaceName_{};
Variant value_;
};
class AllPropertiesGetter
{
public:
AllPropertiesGetter(IProxy& proxy);
std::map<std::string, Variant> onInterface(const std::string& interfaceName);
private:
IProxy& proxy_;
};
class AsyncAllPropertiesGetter
{
public:
AsyncAllPropertiesGetter(IProxy& proxy);
AsyncAllPropertiesGetter& onInterface(const std::string& interfaceName);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
std::future<std::map<std::string, Variant>> getResultAsFuture();
private:
IProxy& proxy_;
const std::string* interfaceName_{};
};
}
+185 -19
View File
@@ -293,7 +293,7 @@ namespace sdbus {
template <typename _Function>
inline PropertyRegistrator& PropertyRegistrator::withGetter(_Function&& callback)
{
static_assert(function_traits<_Function>::arity == 0, "Property getter function must not take any arguments");
static_assert(function_argument_count_v<_Function> == 0, "Property getter function must not take any arguments");
static_assert(!std::is_void<function_result_t<_Function>>::value, "Property getter function must return property value");
if (propertySignature_.empty())
@@ -311,7 +311,7 @@ namespace sdbus {
template <typename _Function>
inline PropertyRegistrator& PropertyRegistrator::withSetter(_Function&& callback)
{
static_assert(function_traits<_Function>::arity == 1, "Property setter function must take one parameter - the property value");
static_assert(function_argument_count_v<_Function> == 1, "Property setter function must take one parameter - the property value");
static_assert(std::is_void<function_result_t<_Function>>::value, "Property setter function must not return any value");
if (propertySignature_.empty())
@@ -704,7 +704,7 @@ namespace sdbus {
{
}
inline void SignalUnsubscriber::onInterface(std::string interfaceName)
inline void SignalUnsubscriber::onInterface(const std::string& interfaceName)
{
proxy_.unregisterSignalHandler(interfaceName, signalName_);
}
@@ -719,17 +719,56 @@ namespace sdbus {
{
}
inline sdbus::Variant PropertyGetter::onInterface(const std::string& interfaceName)
inline Variant PropertyGetter::onInterface(const std::string& interfaceName)
{
sdbus::Variant var;
proxy_
.callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName, propertyName_)
.storeResultsTo(var);
Variant var;
proxy_.callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName, propertyName_)
.storeResultsTo(var);
return var;
}
/*** ------------------- ***/
/*** AsyncPropertyGetter ***/
/*** ------------------- ***/
inline AsyncPropertyGetter::AsyncPropertyGetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy)
, propertyName_(propertyName)
{
}
inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = &interfaceName;
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback)
{
static_assert(std::is_invocable_r_v<void, _Function, const Error*, Variant>, "Property get callback function must accept Error* and property value as Variant");
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_)
.uponReplyInvoke(std::forward<_Function>(callback));
}
inline std::future<Variant> AsyncPropertyGetter::getResultAsFuture()
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_)
.getResultAsFuture<Variant>();
}
/*** -------------- ***/
/*** PropertySetter ***/
/*** -------------- ***/
@@ -740,9 +779,9 @@ namespace sdbus {
{
}
inline PropertySetter& PropertySetter::onInterface(std::string interfaceName)
inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = std::move(interfaceName);
interfaceName_ = &interfaceName;
return *this;
}
@@ -750,17 +789,144 @@ namespace sdbus {
template <typename _Value>
inline void PropertySetter::toValue(const _Value& value)
{
PropertySetter::toValue(sdbus::Variant{value});
PropertySetter::toValue(Variant{value});
}
inline void PropertySetter::toValue(const sdbus::Variant& value)
template <typename _Value>
inline void PropertySetter::toValue(const _Value& value, dont_expect_reply_t)
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
PropertySetter::toValue(Variant{value}, dont_expect_reply);
}
proxy_
.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName_, propertyName_, value);
inline void PropertySetter::toValue(const Variant& value)
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
proxy_.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, value);
}
inline void PropertySetter::toValue(const Variant& value, dont_expect_reply_t)
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
proxy_.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, value)
.dontExpectReply();
}
/*** ------------------- ***/
/*** AsyncPropertySetter ***/
/*** ------------------- ***/
inline AsyncPropertySetter::AsyncPropertySetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy)
, propertyName_(propertyName)
{
}
inline AsyncPropertySetter& AsyncPropertySetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = &interfaceName;
return *this;
}
template <typename _Value>
inline AsyncPropertySetter& AsyncPropertySetter::toValue(_Value&& value)
{
return AsyncPropertySetter::toValue(Variant{std::forward<_Value>(value)});
}
inline AsyncPropertySetter& AsyncPropertySetter::toValue(Variant value)
{
value_ = std::move(value);
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncPropertySetter::uponReplyInvoke(_Function&& callback)
{
static_assert(std::is_invocable_r_v<void, _Function, const Error*>, "Property set callback function must accept Error* only");
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, std::move(value_))
.uponReplyInvoke(std::forward<_Function>(callback));
}
inline std::future<void> AsyncPropertySetter::getResultAsFuture()
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, std::move(value_))
.getResultAsFuture<>();
}
/*** ------------------- ***/
/*** AllPropertiesGetter ***/
/*** ------------------- ***/
inline AllPropertiesGetter::AllPropertiesGetter(IProxy& proxy)
: proxy_(proxy)
{
}
inline std::map<std::string, Variant> AllPropertiesGetter::onInterface(const std::string& interfaceName)
{
std::map<std::string, Variant> props;
proxy_.callMethod("GetAll")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName)
.storeResultsTo(props);
return props;
}
/*** ------------------------ ***/
/*** AsyncAllPropertiesGetter ***/
/*** ------------------------ ***/
inline AsyncAllPropertiesGetter::AsyncAllPropertiesGetter(IProxy& proxy)
: proxy_(proxy)
{
}
inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = &interfaceName;
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback)
{
static_assert( std::is_invocable_r_v<void, _Function, const Error*, std::map<std::string, Variant>>
, "All properties get callback function must accept Error* and a map of property names to their values" );
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_)
.uponReplyInvoke(std::forward<_Function>(callback));
}
inline std::future<std::map<std::string, Variant>> AsyncAllPropertiesGetter::getResultAsFuture()
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_)
.getResultAsFuture<std::map<std::string, Variant>>();
}
}
+110 -12
View File
@@ -83,7 +83,7 @@ namespace sdbus {
virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Calls method on the proxied D-Bus object
* @brief Calls method on the D-Bus object
*
* @param[in] message Message representing a method call
* @param[in] timeout Timeout for dbus call in microseconds
@@ -109,7 +109,7 @@ namespace sdbus {
MethodReply callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout);
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyCallback Handler for the async reply
@@ -133,7 +133,7 @@ namespace sdbus {
PendingAsyncCall callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, const std::chrono::duration<_Rep, _Period>& timeout);
/*!
* @brief Registers a handler for the desired signal emitted by the proxied D-Bus object
* @brief Registers a handler for the desired signal emitted by the D-Bus object
*
* @param[in] interfaceName Name of an interface that the signal belongs to
* @param[in] signalName Name of the signal
@@ -178,7 +178,7 @@ namespace sdbus {
virtual void unregister() = 0;
/*!
* @brief Calls method on the proxied D-Bus object
* @brief Calls method on the D-Bus object
*
* @param[in] methodName Name of the method
* @return A helper object for convenient invocation of the method
@@ -199,7 +199,7 @@ namespace sdbus {
[[nodiscard]] MethodInvoker callMethod(const std::string& methodName);
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] methodName Name of the method
* @return A helper object for convenient asynchronous invocation of the method
@@ -223,7 +223,7 @@ namespace sdbus {
[[nodiscard]] AsyncMethodInvoker callMethodAsync(const std::string& methodName);
/*!
* @brief Registers signal handler for a given signal of the proxied D-Bus object
* @brief Registers signal handler for a given signal of the D-Bus object
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient registration of the signal handler
@@ -243,7 +243,7 @@ namespace sdbus {
[[nodiscard]] SignalSubscriber uponSignal(const std::string& signalName);
/*!
* @brief Unregisters signal handler of a given signal of the proxied D-Bus object
* @brief Unregisters signal handler of a given signal of the D-Bus object
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient unregistration of the signal handler
@@ -260,7 +260,7 @@ namespace sdbus {
[[nodiscard]] SignalUnsubscriber muteSignal(const std::string& signalName);
/*!
* @brief Gets value of a property of the proxied D-Bus object
* @brief Gets value of a property of the D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient getting of property value
@@ -279,23 +279,101 @@ namespace sdbus {
[[nodiscard]] PropertyGetter getProperty(const std::string& propertyName);
/*!
* @brief Sets value of a property of the proxied D-Bus object
* @brief Gets value of a property of the D-Bus object asynchronously
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient asynchronous getting of property value
*
* This is a high-level, convenience way of reading D-Bus property values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* std::future<sdbus::Variant> state = object.getPropertyAsync("state").onInterface("com.kistler.foo").getResultAsFuture();
* auto callback = [](const sdbus::Error* err, const sdbus::Variant& value){ ... };
* object.getPropertyAsync("state").onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback));
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AsyncPropertyGetter getPropertyAsync(const std::string& propertyName);
/*!
* @brief Sets value of a property of the D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient setting of property value
*
* This is a high-level, convenience way of writing D-Bus property values that abstracts
* from the D-Bus message concept.
* Setting property value with NoReply flag is also supported.
*
* Example of use:
* @code
* int state = ...;
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state);
* // Or we can just send the set message call without waiting for the reply
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state, dont_expect_reply);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] PropertySetter setProperty(const std::string& propertyName);
/*!
* @brief Sets value of a property of the D-Bus object asynchronously
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient asynchronous setting of property value
*
* This is a high-level, convenience way of writing D-Bus property values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* int state = ...;
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state);
* // We can wait until the set operation finishes by waiting on the future
* std::future<void> res = object_.setPropertyAsync("state").onInterface("com.kistler.foo").toValue(state).getResultAsFuture();
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] PropertySetter setProperty(const std::string& propertyName);
[[nodiscard]] AsyncPropertySetter setPropertyAsync(const std::string& propertyName);
/*!
* @brief Gets values of all properties of the D-Bus object
*
* @return A helper object for convenient getting of properties' values
*
* This is a high-level, convenience way of reading D-Bus properties' values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* auto props = object.getAllProperties().onInterface("com.kistler.foo");
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AllPropertiesGetter getAllProperties();
/*!
* @brief Gets values of all properties of the D-Bus object asynchronously
*
* @return A helper object for convenient asynchronous getting of properties' values
*
* This is a high-level, convenience way of reading D-Bus properties' values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* auto callback = [](const sdbus::Error* err, const std::map<std::string, Variant>>& properties){ ... };
* auto props = object.getAllPropertiesAsync().onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback));
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AsyncAllPropertiesGetter getAllPropertiesAsync();
/*!
* @brief Provides D-Bus connection used by the proxy
@@ -326,7 +404,7 @@ namespace sdbus {
virtual const Message* getCurrentlyProcessedMessage() const = 0;
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyCallback Handler for the async reply
@@ -445,11 +523,31 @@ namespace sdbus {
return PropertyGetter(*this, propertyName);
}
inline AsyncPropertyGetter IProxy::getPropertyAsync(const std::string& propertyName)
{
return AsyncPropertyGetter(*this, propertyName);
}
inline PropertySetter IProxy::setProperty(const std::string& propertyName)
{
return PropertySetter(*this, propertyName);
}
inline AsyncPropertySetter IProxy::setPropertyAsync(const std::string& propertyName)
{
return AsyncPropertySetter(*this, propertyName);
}
inline AllPropertiesGetter IProxy::getAllProperties()
{
return AllPropertiesGetter(*this);
}
inline AsyncAllPropertiesGetter IProxy::getAllPropertiesAsync()
{
return AsyncAllPropertiesGetter(*this);
}
/*!
* @brief Creates a proxy object for a specific remote D-Bus object
*
+39 -3
View File
@@ -138,16 +138,52 @@ namespace sdbus {
return proxy_->getProperty(propertyName).onInterface(interfaceName);
}
template <typename _Function>
PendingAsyncCall GetAsync(const std::string& interfaceName, const std::string& propertyName, _Function&& callback)
{
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
std::future<sdbus::Variant> GetAsync(const std::string& interfaceName, const std::string& propertyName, with_future_t)
{
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture();
}
void Set(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value)
{
proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value);
}
void Set(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, dont_expect_reply_t)
{
proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply);
}
template <typename _Function>
PendingAsyncCall SetAsync(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, _Function&& callback)
{
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
}
std::future<void> SetAsync(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, with_future_t)
{
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture();
}
std::map<std::string, sdbus::Variant> GetAll(const std::string& interfaceName)
{
std::map<std::string, sdbus::Variant> props;
proxy_->callMethod("GetAll").onInterface(INTERFACE_NAME).withArguments(interfaceName).storeResultsTo(props);
return props;
return proxy_->getAllProperties().onInterface(interfaceName);
}
template <typename _Function>
PendingAsyncCall GetAllAsync(const std::string& interfaceName, _Function&& callback)
{
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
std::future<std::map<std::string, sdbus::Variant>> GetAllAsync(const std::string& interfaceName, with_future_t)
{
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture();
}
private:
+3
View File
@@ -93,6 +93,9 @@ namespace sdbus {
// Tag denoting an asynchronous call that returns std::future as a handle
struct with_future_t { explicit with_future_t() = default; };
inline constexpr with_future_t with_future{};
// Tag denoting a call where the reply shouldn't be waited for
struct dont_expect_reply_t { explicit dont_expect_reply_t() = default; };
inline constexpr dont_expect_reply_t dont_expect_reply{};
// Template specializations for getting D-Bus signatures from C++ types
template <typename _T>