blob: 8553bd41604e163f2d7775563d2cf0e9524c82a8 [file] [log] [blame]
#pragma once
#include <sdbusplus/bus.hpp>
#include <sdbusplus/sdbus.hpp>
#include <type_traits>
namespace sdbusplus
{
namespace server
{
namespace object
{
namespace details
{
/** Helper functions to detect if member exists in a class */
/** Test if emit_added() exists in T return std::true_type */
template <class T>
constexpr auto has_emit_added_helper(int)
-> decltype(std::declval<T>().emit_added(), std::true_type{});
/** If the above test fails, fall back to this to return std::false_type */
template <class>
constexpr std::false_type has_emit_added_helper(...);
/** Invoke the test with an int so it first resolves to
* has_emit_added_helper(int), and when it fails, it resovles to
* has_emit_added_helper(...) thanks to SFINAE.
* So the return type is std::true_type if emit_added() exists in T and
* std::false_type otherwise */
template <class T>
using has_emit_added = decltype(has_emit_added_helper<T>(0));
/** Templates to allow multiple inheritance via template parameters.
*
* These allow an object to group multiple dbus interface bindings into a
* single class.
*/
template <class T, class... Rest>
struct compose_impl : T, compose_impl<Rest...>
{
compose_impl(bus_t& bus, const char* path) :
T(bus, path), compose_impl<Rest...>(bus, path)
{}
};
/** Specialization for single element. */
template <class T>
struct compose_impl<T> : T
{
compose_impl(bus_t& bus, const char* path) : T(bus, path)
{}
};
/** Default compose operation for variadic arguments. */
template <class... Args>
struct compose : compose_impl<Args...>
{
compose(bus_t& bus, const char* path) : compose_impl<Args...>(bus, path)
{}
};
/** Specialization for zero variadic arguments. */
template <>
struct compose<>
{
compose(bus_t& /*bus*/, const char* /*path*/)
{}
};
} // namespace details
/** Class to compose multiple dbus interfaces and object signals.
*
* Any number of classes representing a dbus interface may be composed into
* a single dbus object. The interfaces will be created first and hooked
* into the object space via the 'add_object_vtable' calls. Afterwards,
* a signal will be emitted for the whole object to indicate all new
* interfaces via 'sd_bus_emit_object_added'.
*
* Similar, on destruction, the interfaces are removed (unref'd) and the
* 'sd_bus_emit_object_removed' signals are emitted.
*
*/
template <class... Args>
struct object : details::compose<Args...>
{
/* Define all of the basic class operations:
* Not allowed:
* - Default constructor to avoid nullptrs.
* - Copy operations due to internal unique_ptr.
* - Move operations.
* Allowed:
* - Destructor.
*/
object() = delete;
object(const object&) = delete;
object& operator=(const object&) = delete;
object(object&&) = delete;
object& operator=(object&&) = delete;
enum class action
{
emit_object_added,
emit_interface_added,
defer_emit,
};
/** Construct an 'object' on a bus with a path.
*
* @param[in] bus - The bus to place the object on.
* @param[in] path - The path the object resides at.
* @param[in] deferSignal - Set to true if emit_object_added should be
* deferred. This would likely be true if the
* object needs custom property init before the
* signal can be sent.
*/
object(bus_t& bus, const char* path,
action act = action::emit_object_added) :
details::compose<Args...>(bus, path),
__sdbusplus_server_object_bus(bus.get(), bus.getInterface()),
__sdbusplus_server_object_path(path),
__sdbusplus_server_object_emitremoved(false),
__sdbusplus_server_object_intf(bus.getInterface())
{
// Default ctor
check_action(act);
}
object(bus_t& bus, const char* path, bool deferSignal) :
object(bus, path,
deferSignal ? action::defer_emit : action::emit_object_added)
{
// Delegate to default ctor
}
~object()
{
if (__sdbusplus_server_object_emitremoved)
{
__sdbusplus_server_object_intf->sd_bus_emit_object_removed(
__sdbusplus_server_object_bus.get(),
__sdbusplus_server_object_path.c_str());
}
}
/** Emit the 'object-added' signal, if not already sent. */
void emit_object_added()
{
if (!__sdbusplus_server_object_emitremoved)
{
__sdbusplus_server_object_intf->sd_bus_emit_object_added(
__sdbusplus_server_object_bus.get(),
__sdbusplus_server_object_path.c_str());
__sdbusplus_server_object_emitremoved = true;
}
}
private:
// These member names are purposefully chosen as long and, hopefully,
// unique. Since an object is 'composed' via multiple-inheritence,
// all members need to have unique names to ensure there is no
// ambiguity.
bus_t __sdbusplus_server_object_bus;
std::string __sdbusplus_server_object_path;
bool __sdbusplus_server_object_emitremoved;
SdBusInterface* __sdbusplus_server_object_intf;
/** Detect if the interface has emit_added() and invoke it */
template <class T>
void maybe_emit()
{
if constexpr (details::has_emit_added<T>())
{
T::emit_added();
}
}
/** Check and run the action */
void check_action(action act)
{
if (act == action::emit_object_added)
{
emit_object_added();
}
else if (act == action::emit_interface_added)
{
(maybe_emit<Args>(), ...);
}
// Otherwise, do nothing
}
};
} // namespace object
template <class... Args>
using object_t = object::object<Args...>;
} // namespace server
} // namespace sdbusplus