Line data Source code
1 : // Webthing-CPP
2 : // SPDX-FileCopyrightText: 2023-present Benno Waldhauer
3 : // SPDX-License-Identifier: MIT
4 :
5 : #pragma once
6 :
7 : #include <optional>
8 : #include <bw/webthing/json.hpp>
9 : #include <bw/webthing/utils.hpp>
10 :
11 : namespace bw::webthing {
12 :
13 : class Action;
14 :
15 : json action_status_message(const Action& action);
16 : json action_status_message(std::shared_ptr<Action> action);
17 :
18 : struct ActionBehavior
19 : {
20 : std::function<void (json)> notify_thing;
21 : std::function<void ()> perform_action;
22 : std::function<void ()> cancel_action;
23 : std::function<void* ()> get_thing;
24 : };
25 :
26 :
27 5 : template<class T> ActionBehavior make_action_behavior(T* thing,
28 : std::function<void ()> perform_action = nullptr,
29 : std::function<void ()> cancel_action = nullptr)
30 : {
31 : return {
32 6 : [thing](auto action_status){ thing->action_notify(action_status); },
33 5 : std::move(perform_action),
34 5 : std::move(cancel_action),
35 0 : [thing]{ return thing; }
36 10 : };
37 5 : }
38 :
39 : template <typename T, typename = void>
40 : struct has_perform_action : std::false_type {};
41 :
42 : template <typename T>
43 : struct has_perform_action<T, std::void_t<decltype(&T::perform_action)>> : std::true_type {};
44 :
45 : template <typename T, typename = void>
46 : struct has_cancel_action : std::false_type {};
47 :
48 : template <typename T>
49 : struct has_cancel_action<T, std::void_t<decltype(&T::cancel_action)>> : std::true_type {};
50 :
51 : template <typename T>
52 : typename std::enable_if<has_cancel_action<T>::value>::type
53 1 : execute_cancel_action(T& action_impl)
54 : {
55 1 : (action_impl.*&T::cancel_action)();
56 1 : }
57 :
58 : template <typename T>
59 : typename std::enable_if<!has_cancel_action<T>::value>::type
60 1 : execute_cancel_action(T& action_impl)
61 : {
62 : // action_impl has no cancel_action that could be executed
63 1 : }
64 :
65 5 : template<class T, class A> ActionBehavior make_action_behavior(T* thing, A* action_impl)
66 : {
67 : return {
68 20 : [thing](auto action_status){ thing->action_notify(action_status); },
69 15 : [action_impl]{ action_impl->perform_action(); },
70 12 : [action_impl]{ execute_cancel_action(*action_impl); },
71 7 : [thing]{ return thing; }
72 10 : };
73 5 : }
74 :
75 : //An Action represents an individual action on a thing.
76 : class Action : public std::enable_shared_from_this<Action>
77 : {
78 : public:
79 21 : Action(std::string id, ActionBehavior action_behavior, std::string name, std::optional<json> input = std::nullopt)
80 21 : : id(id)
81 21 : , action_behavior(action_behavior)
82 21 : , name(name)
83 21 : , input(input)
84 21 : , href("/actions/" + name + "/" + id)
85 42 : , status("created")
86 63 : , time_requested(bw::webthing::timestamp())
87 : {
88 21 : }
89 :
90 5 : template<class T, class A> Action(std::string id, T* thing, A* action_impl, std::string name, std::optional<json> input = std::nullopt)
91 5 : : Action(id, make_action_behavior(thing, action_impl), name, input)
92 : {
93 5 : }
94 :
95 : // Get the action description of the action as a json object.
96 49 : json as_action_description() const
97 : {
98 49 : json description;
99 49 : description[name]["href"] = href_prefix + href;
100 49 : description[name]["timeRequested"] = time_requested;
101 49 : description[name]["status"] = status;
102 :
103 49 : if(input)
104 41 : description[name]["input"] = *input;
105 :
106 49 : if(time_completed)
107 16 : description[name]["timeCompleted"] = *time_completed;
108 :
109 49 : return description;
110 0 : }
111 :
112 : // Set the prefix of any hrefs associated with this action.
113 19 : void set_href_prefix(const std::string& prefix)
114 : {
115 19 : href_prefix = prefix;
116 19 : }
117 :
118 16 : std::string get_id() const
119 : {
120 16 : return id;
121 : }
122 :
123 4 : std::string get_name() const
124 : {
125 4 : return name;
126 : }
127 :
128 2 : std::string get_href() const
129 : {
130 2 : return href_prefix + href;
131 : }
132 :
133 8 : std::string get_status() const
134 : {
135 8 : return status;
136 : }
137 :
138 2 : std::string get_time_requested() const
139 : {
140 2 : return time_requested;
141 : }
142 :
143 4 : std::optional<std::string> get_time_completed() const
144 : {
145 4 : return time_completed;
146 : }
147 :
148 11 : std::optional<json> get_input() const
149 : {
150 11 : return input;
151 : }
152 :
153 4 : template<class T> T* get_thing()
154 : {
155 4 : if(action_behavior.get_thing)
156 4 : return static_cast<T*>(action_behavior.get_thing());
157 0 : return nullptr;
158 : }
159 :
160 : // Start performing the action.
161 10 : void start()
162 : {
163 10 : status = "pending";
164 10 : notify_thing();
165 10 : perform_action();
166 10 : finish();
167 10 : }
168 :
169 : // Finish performing the action.
170 10 : void finish()
171 : {
172 10 : status = "completed";
173 10 : time_completed = timestamp();
174 10 : notify_thing();
175 10 : }
176 :
177 10 : void perform_action()
178 : {
179 10 : if(action_behavior.perform_action)
180 10 : action_behavior.perform_action();
181 10 : }
182 :
183 5 : void cancel()
184 : {
185 5 : if(action_behavior.cancel_action)
186 3 : action_behavior.cancel_action();
187 5 : }
188 :
189 : private:
190 20 : void notify_thing()
191 : {
192 20 : if(action_behavior.notify_thing)
193 : {
194 : try
195 : {
196 : // Pass this as shared_ptr to ensure Action remains alive during callback execution
197 : // this might be necessary, as an Action is interacted with from different threads.
198 20 : action_behavior.notify_thing(action_status_message(shared_from_this()));
199 : }
200 4 : catch(std::bad_weak_ptr&)
201 : {
202 4 : action_behavior.notify_thing(action_status_message(*this));
203 4 : }
204 : }
205 20 : }
206 :
207 : std::string id;
208 : ActionBehavior action_behavior;
209 : std::string name;
210 : std::optional<json> input;
211 : std::string href_prefix;
212 : std::string href;
213 : std::string status;
214 : std::string time_requested;
215 : std::optional<std::string> time_completed;
216 : };
217 :
218 38 : inline json action_status_message(const Action& action)
219 : {
220 38 : json description = action.as_action_description();
221 : return json({
222 : {"messageType", "actionStatus"},
223 : {"data", description}
224 304 : });
225 228 : }
226 :
227 33 : inline json action_status_message(std::shared_ptr<Action> action)
228 : {
229 33 : return action_status_message(*action);
230 : }
231 :
232 : } // bw::webthing
|