blob: 5934a7e32b2ceb9402ebd6c790e50a429a8ce0b8 [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 Williamsd2149042023-05-10 07:50:13 -0500136 return callback(
137 [bus = get_busp(ctx.get_bus()),
138 msg = std::move(msg)](auto cb, auto data) mutable {
139 return sd_bus_call_async(bus, nullptr, msg.get(), cb, data, 0);
140 }) | execution::then([](message_t&& m) { return m.unpack<Rs...>(); });
Patrick Williams10010b12022-08-28 14:56:07 -0500141 }
142
143 /** Get a property.
144 *
145 * @tparam T - The type of the property.
146 *
147 * @param[in] ctx - The context to use.
148 * @param[in] property - The property name.
149 *
150 * @return A Sender which completes with T as the property value.
151 */
152 template <typename T>
153 auto get_property(context& ctx, sv_ref property) const
154 requires((S) && (P) && (I))
155 {
156 using result_t = std::variant<T>;
157 auto prop_intf = proxy(s, p, dbus_prop_intf);
158
159 return prop_intf.template call<result_t>(ctx, "Get", c_str(i),
160 property.data()) |
161 execution::then([](result_t&& v) { return std::get<T>(v); });
162 }
163
164 /** Get all properties.
165 *
166 * @tparam V - The variant type of all possible properties.
167 *
168 * @param[in] ctx - The context to use.
169 *
170 * @return A Sender which completes with unordered_map<string, V>.
171 */
172 template <typename V>
173 auto get_all_properties(context& ctx) const
174 requires((S) && (P) && (I))
175 {
176 using result_t = std::unordered_map<std::string, V>;
177 auto prop_intf = proxy(s, p, dbus_prop_intf);
178
179 return prop_intf.template call<result_t>(ctx, "GetAll", c_str(i));
180 }
181
182 /** Set a property.
183 *
184 * @tparam T - The type of the property (usually deduced by the compiler).
185 *
186 * @param[in] ctx - The context to use.
187 * @param[in] property - The property name.
188 * @param[in] value - The value to set.
189 *
190 * @return A Sender which completes void when the property is set.
191 */
192 template <typename T>
193 auto set_property(context& ctx, sv_ref property, T&& value) const
194 requires((S) && (P) && (I))
195 {
196 auto prop_intf = proxy(s, p, dbus_prop_intf);
Patrick Williams1f3efd82023-04-25 15:52:56 -0500197 return prop_intf.template call<>(
198 ctx, "Set", c_str(i), property.data(),
199 std::variant<std::decay_t<T>>{std::forward<T>(value)});
Patrick Williams10010b12022-08-28 14:56:07 -0500200 }
201
202 private:
Patrick Williamsb274c312023-04-25 15:30:01 -0500203 static constexpr auto dbus_prop_intf = "org.freedesktop.DBus.Properties";
Patrick Williams10010b12022-08-28 14:56:07 -0500204
205 // Helper to get the underlying c-string of a string_view or string.
206 static auto c_str(string_ref v)
207 {
208 if constexpr (Preserved)
209 {
210 return v.c_str();
211 }
212 else
213 {
214 return v.data();
215 }
216 }
217
218 value_t<S> s = {};
219 value_t<P> p = {};
220 value_t<I> i = {};
221};
222
223} // namespace proxy_ns
224
225// clang currently has problems with the intersect of default template
226// parameters and concepts. I've opened llvm/llvm-project#57646 and added
227// this indirect.
228using proxy = proxy_ns::proxy<>;
229
230// Sometimes it is useful to hold onto a proxy, such as in a class member, so
231// define a type alias for one which can be safely held.
232using finalized_proxy = proxy_ns::proxy<true, true, true, true>;
233
234} // namespace sdbusplus::async