Patrick Williams | 10010b1 | 2022-08-28 14:56:07 -0500 | [diff] [blame] | 1 | #pragma once |
| 2 | |
| 3 | #include <sdbusplus/async/callback.hpp> |
| 4 | #include <sdbusplus/async/context.hpp> |
| 5 | #include <sdbusplus/message.hpp> |
| 6 | |
| 7 | #include <string> |
| 8 | #include <string_view> |
| 9 | #include <type_traits> |
| 10 | #include <unordered_map> |
| 11 | #include <variant> |
| 12 | |
| 13 | namespace sdbusplus::async |
| 14 | { |
| 15 | namespace proxy_ns |
| 16 | { |
| 17 | /** A (client-side) proxy to a dbus object. |
| 18 | * |
| 19 | * A dbus object is referenced by 3 address pieces: |
| 20 | * - The service hosting the object. |
| 21 | * - The path the object resides at. |
| 22 | * - The interface the object implements. |
| 23 | * The proxy is a holder of these 3 addresses. |
| 24 | * |
| 25 | * One all 3 pieces of the address are known by the proxy, the proxy |
| 26 | * can be used to perform normal dbus operations: method-call, get-property |
| 27 | * set-property, get-all-properties. |
| 28 | * |
| 29 | * Addresses are supplied to the object by calling the appropriate method: |
| 30 | * service, path, interface. These methods return a _new_ object with the |
| 31 | * new information filled in. |
| 32 | * |
| 33 | * If all pieces are known at compile-time it is suggested to be constructed |
| 34 | * similar to the following: |
| 35 | * |
| 36 | * ``` |
| 37 | * constexpr auto systemd = sdbusplus::async::proxy() |
| 38 | * .service("org.freedesktop.systemd1") |
| 39 | * .path("/org/freedesktop/systemd1") |
| 40 | * .interface("org.freedesktop.systemd1.Manager"); |
| 41 | * ``` |
| 42 | * |
| 43 | * The proxy object can be filled as information is available and attempts |
| 44 | * to be as effecient as possible (supporting constexpr construction and |
| 45 | * using std::string_view mostly). In some cases it is necessary for the |
| 46 | * proxy to leave a scope where it would be no longer safe to use the |
| 47 | * previously-supplied string_views. The `preserve` operation can be used |
| 48 | * to transform an existing proxy into one which is safe to leave (because |
| 49 | * it uses less-efficient but safe std::string values). |
| 50 | */ |
| 51 | template <bool S = false, bool P = false, bool I = false, |
| 52 | bool Preserved = false> |
| 53 | struct proxy : private sdbusplus::bus::details::bus_friend |
| 54 | { |
| 55 | // Some typedefs to reduce template noise... |
| 56 | |
| 57 | using string_t = |
| 58 | std::conditional_t<Preserved, std::string, std::string_view>; |
| 59 | using string_ref = const string_t&; |
| 60 | using sv_ref = const std::string_view&; |
| 61 | |
| 62 | template <bool V> |
| 63 | using value_t = std::conditional_t<V, string_t, std::monostate>; |
| 64 | template <bool V> |
| 65 | using value_ref = const value_t<V>&; |
| 66 | |
| 67 | // Default constructor should only work for the "all empty" case. |
| 68 | constexpr proxy() |
| 69 | requires(!S && !P && !I) |
| 70 | = default; |
| 71 | constexpr proxy() |
| 72 | requires(S || P || I) |
| 73 | = delete; |
| 74 | |
| 75 | // Construtor allowing all 3 to be passed in. |
| 76 | constexpr proxy(value_ref<S> s, value_ref<P> p, value_ref<I> i) : |
| 77 | s(s), p(p), i(i){}; |
| 78 | |
| 79 | // Functions to assign address fields. |
| 80 | constexpr auto service(string_ref s) const noexcept |
| 81 | requires(!S) |
| 82 | { |
| 83 | return proxy<true, P, I, Preserved>{s, this->p, this->i}; |
| 84 | } |
| 85 | constexpr auto path(string_ref p) const noexcept |
| 86 | requires(!P) |
| 87 | { |
| 88 | return proxy<S, true, I, Preserved>{this->s, p, this->i}; |
| 89 | } |
| 90 | constexpr auto interface(string_ref i) const noexcept |
| 91 | requires(!I) |
| 92 | { |
| 93 | return proxy<S, P, true, Preserved>{this->s, this->p, i}; |
| 94 | } |
| 95 | |
| 96 | /** Make a copyable / returnable proxy. |
| 97 | * |
| 98 | * Since proxy deals with string_view by default, for efficiency, |
| 99 | * there are cases where it would be dangerous for a proxy object to |
| 100 | * leave a scope either by a return or a pass into a lambda. This |
| 101 | * function will convert an existing proxy into one backed by |
| 102 | * `std::string` so that it can safely leave a scope. |
| 103 | */ |
| 104 | auto preserve() const noexcept |
| 105 | requires(!Preserved) |
| 106 | { |
Patrick Williams | f1d999b | 2024-01-24 08:05:21 -0600 | [diff] [blame] | 107 | using result_t = proxy<S, P, I, true>; |
Ed Tanous | e12a23c | 2024-02-22 15:28:58 -0800 | [diff] [blame^] | 108 | return result_t(typename result_t::template value_t<S>(this->s), |
| 109 | typename result_t::template value_t<P>(this->p), |
| 110 | typename result_t::template value_t<I>(this->i)); |
Patrick Williams | 10010b1 | 2022-08-28 14:56:07 -0500 | [diff] [blame] | 111 | } |
| 112 | |
| 113 | /** Perform a method call. |
| 114 | * |
| 115 | * @tparam Rs - The return type(s) of the method call. |
| 116 | * @tparam Ss - The parameter type(s) of the method call. |
| 117 | * |
| 118 | * @param[in] ctx - The context to use. |
| 119 | * @param[in] method - The method name. |
| 120 | * @param[in] ss - The calling parameters. |
| 121 | * |
| 122 | * @return A Sender which completes with either { void, Rs, tuple<Rs...> }. |
| 123 | */ |
| 124 | template <typename... Rs, typename... Ss> |
| 125 | auto call(context& ctx, sv_ref method, Ss&&... ss) const |
| 126 | requires((S) && (P) && (I)) |
| 127 | { |
| 128 | // Create the method_call message. |
| 129 | auto msg = ctx.get_bus().new_method_call(c_str(s), c_str(p), c_str(i), |
| 130 | method.data()); |
| 131 | if constexpr (sizeof...(Ss) > 0) |
| 132 | { |
| 133 | msg.append(std::forward<Ss>(ss)...); |
| 134 | } |
| 135 | |
| 136 | // Use 'callback' to perform the operation and "then" "unpack" the |
| 137 | // contents. |
Patrick Williams | 6db8838 | 2023-10-20 11:18:17 -0500 | [diff] [blame] | 138 | return callback([bus = get_busp(ctx), |
| 139 | msg = std::move(msg)](auto cb, auto data) mutable { |
Patrick Williams | d214904 | 2023-05-10 07:50:13 -0500 | [diff] [blame] | 140 | return sd_bus_call_async(bus, nullptr, msg.get(), cb, data, 0); |
| 141 | }) | execution::then([](message_t&& m) { return m.unpack<Rs...>(); }); |
Patrick Williams | 10010b1 | 2022-08-28 14:56:07 -0500 | [diff] [blame] | 142 | } |
| 143 | |
| 144 | /** Get a property. |
| 145 | * |
| 146 | * @tparam T - The type of the property. |
| 147 | * |
| 148 | * @param[in] ctx - The context to use. |
| 149 | * @param[in] property - The property name. |
| 150 | * |
| 151 | * @return A Sender which completes with T as the property value. |
| 152 | */ |
| 153 | template <typename T> |
| 154 | auto get_property(context& ctx, sv_ref property) const |
| 155 | requires((S) && (P) && (I)) |
| 156 | { |
| 157 | using result_t = std::variant<T>; |
| 158 | auto prop_intf = proxy(s, p, dbus_prop_intf); |
| 159 | |
| 160 | return prop_intf.template call<result_t>(ctx, "Get", c_str(i), |
| 161 | property.data()) | |
| 162 | execution::then([](result_t&& v) { return std::get<T>(v); }); |
| 163 | } |
| 164 | |
| 165 | /** Get all properties. |
| 166 | * |
| 167 | * @tparam V - The variant type of all possible properties. |
| 168 | * |
| 169 | * @param[in] ctx - The context to use. |
| 170 | * |
| 171 | * @return A Sender which completes with unordered_map<string, V>. |
| 172 | */ |
| 173 | template <typename V> |
| 174 | auto get_all_properties(context& ctx) const |
| 175 | requires((S) && (P) && (I)) |
| 176 | { |
| 177 | using result_t = std::unordered_map<std::string, V>; |
| 178 | auto prop_intf = proxy(s, p, dbus_prop_intf); |
| 179 | |
| 180 | return prop_intf.template call<result_t>(ctx, "GetAll", c_str(i)); |
| 181 | } |
| 182 | |
| 183 | /** Set a property. |
| 184 | * |
| 185 | * @tparam T - The type of the property (usually deduced by the compiler). |
| 186 | * |
| 187 | * @param[in] ctx - The context to use. |
| 188 | * @param[in] property - The property name. |
| 189 | * @param[in] value - The value to set. |
| 190 | * |
| 191 | * @return A Sender which completes void when the property is set. |
| 192 | */ |
| 193 | template <typename T> |
| 194 | auto set_property(context& ctx, sv_ref property, T&& value) const |
| 195 | requires((S) && (P) && (I)) |
| 196 | { |
| 197 | auto prop_intf = proxy(s, p, dbus_prop_intf); |
Patrick Williams | 1f3efd8 | 2023-04-25 15:52:56 -0500 | [diff] [blame] | 198 | return prop_intf.template call<>( |
| 199 | ctx, "Set", c_str(i), property.data(), |
| 200 | std::variant<std::decay_t<T>>{std::forward<T>(value)}); |
Patrick Williams | 10010b1 | 2022-08-28 14:56:07 -0500 | [diff] [blame] | 201 | } |
| 202 | |
| 203 | private: |
Patrick Williams | b274c31 | 2023-04-25 15:30:01 -0500 | [diff] [blame] | 204 | static constexpr auto dbus_prop_intf = "org.freedesktop.DBus.Properties"; |
Patrick Williams | 10010b1 | 2022-08-28 14:56:07 -0500 | [diff] [blame] | 205 | |
| 206 | // Helper to get the underlying c-string of a string_view or string. |
| 207 | static auto c_str(string_ref v) |
| 208 | { |
| 209 | if constexpr (Preserved) |
| 210 | { |
| 211 | return v.c_str(); |
| 212 | } |
| 213 | else |
| 214 | { |
| 215 | return v.data(); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | value_t<S> s = {}; |
| 220 | value_t<P> p = {}; |
| 221 | value_t<I> i = {}; |
| 222 | }; |
| 223 | |
| 224 | } // namespace proxy_ns |
| 225 | |
| 226 | // clang currently has problems with the intersect of default template |
| 227 | // parameters and concepts. I've opened llvm/llvm-project#57646 and added |
| 228 | // this indirect. |
| 229 | using proxy = proxy_ns::proxy<>; |
| 230 | |
| 231 | // Sometimes it is useful to hold onto a proxy, such as in a class member, so |
| 232 | // define a type alias for one which can be safely held. |
| 233 | using finalized_proxy = proxy_ns::proxy<true, true, true, true>; |
| 234 | |
| 235 | } // namespace sdbusplus::async |