blob: 3bcb55c70d0789736d6eb1505440f7e379af136f [file] [log] [blame]
Patrick Williams10010b12022-08-28 14:56:07 -05001#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
13namespace sdbusplus::async
14{
15namespace 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 */
51template <bool S = false, bool P = false, bool I = false,
52 bool Preserved = false>
53struct 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 {
107 return proxy<S, P, I, true>{std::string{this->s}, std::string{this->p},
108 std::string{this->i}};
109 }
110
111 /** Perform a method call.
112 *
113 * @tparam Rs - The return type(s) of the method call.
114 * @tparam Ss - The parameter type(s) of the method call.
115 *
116 * @param[in] ctx - The context to use.
117 * @param[in] method - The method name.
118 * @param[in] ss - The calling parameters.
119 *
120 * @return A Sender which completes with either { void, Rs, tuple<Rs...> }.
121 */
122 template <typename... Rs, typename... Ss>
123 auto call(context& ctx, sv_ref method, Ss&&... ss) const
124 requires((S) && (P) && (I))
125 {
126 // Create the method_call message.
127 auto msg = ctx.get_bus().new_method_call(c_str(s), c_str(p), c_str(i),
128 method.data());
129 if constexpr (sizeof...(Ss) > 0)
130 {
131 msg.append(std::forward<Ss>(ss)...);
132 }
133
134 // Use 'callback' to perform the operation and "then" "unpack" the
135 // contents.
Patrick Williams6db88382023-10-20 11:18:17 -0500136 return callback([bus = get_busp(ctx),
137 msg = std::move(msg)](auto cb, auto data) mutable {
Patrick Williamsd2149042023-05-10 07:50:13 -0500138 return sd_bus_call_async(bus, nullptr, msg.get(), cb, data, 0);
139 }) | execution::then([](message_t&& m) { return m.unpack<Rs...>(); });
Patrick Williams10010b12022-08-28 14:56:07 -0500140 }
141
142 /** Get a property.
143 *
144 * @tparam T - The type of the property.
145 *
146 * @param[in] ctx - The context to use.
147 * @param[in] property - The property name.
148 *
149 * @return A Sender which completes with T as the property value.
150 */
151 template <typename T>
152 auto get_property(context& ctx, sv_ref property) const
153 requires((S) && (P) && (I))
154 {
155 using result_t = std::variant<T>;
156 auto prop_intf = proxy(s, p, dbus_prop_intf);
157
158 return prop_intf.template call<result_t>(ctx, "Get", c_str(i),
159 property.data()) |
160 execution::then([](result_t&& v) { return std::get<T>(v); });
161 }
162
163 /** Get all properties.
164 *
165 * @tparam V - The variant type of all possible properties.
166 *
167 * @param[in] ctx - The context to use.
168 *
169 * @return A Sender which completes with unordered_map<string, V>.
170 */
171 template <typename V>
172 auto get_all_properties(context& ctx) const
173 requires((S) && (P) && (I))
174 {
175 using result_t = std::unordered_map<std::string, V>;
176 auto prop_intf = proxy(s, p, dbus_prop_intf);
177
178 return prop_intf.template call<result_t>(ctx, "GetAll", c_str(i));
179 }
180
181 /** Set a property.
182 *
183 * @tparam T - The type of the property (usually deduced by the compiler).
184 *
185 * @param[in] ctx - The context to use.
186 * @param[in] property - The property name.
187 * @param[in] value - The value to set.
188 *
189 * @return A Sender which completes void when the property is set.
190 */
191 template <typename T>
192 auto set_property(context& ctx, sv_ref property, T&& value) const
193 requires((S) && (P) && (I))
194 {
195 auto prop_intf = proxy(s, p, dbus_prop_intf);
Patrick Williams1f3efd82023-04-25 15:52:56 -0500196 return prop_intf.template call<>(
197 ctx, "Set", c_str(i), property.data(),
198 std::variant<std::decay_t<T>>{std::forward<T>(value)});
Patrick Williams10010b12022-08-28 14:56:07 -0500199 }
200
201 private:
Patrick Williamsb274c312023-04-25 15:30:01 -0500202 static constexpr auto dbus_prop_intf = "org.freedesktop.DBus.Properties";
Patrick Williams10010b12022-08-28 14:56:07 -0500203
204 // Helper to get the underlying c-string of a string_view or string.
205 static auto c_str(string_ref v)
206 {
207 if constexpr (Preserved)
208 {
209 return v.c_str();
210 }
211 else
212 {
213 return v.data();
214 }
215 }
216
217 value_t<S> s = {};
218 value_t<P> p = {};
219 value_t<I> i = {};
220};
221
222} // namespace proxy_ns
223
224// clang currently has problems with the intersect of default template
225// parameters and concepts. I've opened llvm/llvm-project#57646 and added
226// this indirect.
227using proxy = proxy_ns::proxy<>;
228
229// Sometimes it is useful to hold onto a proxy, such as in a class member, so
230// define a type alias for one which can be safely held.
231using finalized_proxy = proxy_ns::proxy<true, true, true, true>;
232
233} // namespace sdbusplus::async