blob: da62887cb7d4ca854da17f49979208cef2d861a5 [file] [log] [blame]
Vitaly Buka4615e0d2015-10-14 15:35:12 -07001// Copyright 2015 The Weave Authors. All rights reserved.
Alex Vakulenkoaa3a5592014-08-07 07:24:06 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Stefan Sauer2d16dfa2015-09-25 17:08:35 +02005#include "src/commands/command_instance.h"
Alex Vakulenkoaa3a5592014-08-07 07:24:06 -07006
Alex Vakulenko8dc69af2014-08-07 10:29:42 -07007#include <base/values.h>
Vitaly Buka7b382ac2015-08-03 13:50:01 -07008#include <weave/enum_to_string.h>
Vitaly Buka0801a1f2015-08-14 10:03:46 -07009#include <weave/error.h>
Alex Vakulenko4e4dfd42015-08-06 14:01:42 -070010#include <weave/export.h>
Alex Vakulenko8dc69af2014-08-07 10:29:42 -070011
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020012#include "src/commands/command_queue.h"
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020013#include "src/commands/schema_constants.h"
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020014#include "src/json_error_codes.h"
Vitaly Buka70f77d92015-10-07 15:42:40 -070015#include "src/utils.h"
Alex Vakulenko8dc69af2014-08-07 10:29:42 -070016
Vitaly Bukab6f015a2015-07-09 14:59:23 -070017namespace weave {
Alex Vakulenkoaa3a5592014-08-07 07:24:06 -070018
Vitaly Buka15f59092015-07-24 16:54:32 -070019namespace {
20
Vitaly Buka0209da42015-10-08 00:07:18 -070021const EnumToStringMap<Command::State>::Map kMapStatus[] = {
22 {Command::State::kQueued, "queued"},
23 {Command::State::kInProgress, "inProgress"},
24 {Command::State::kPaused, "paused"},
25 {Command::State::kError, "error"},
26 {Command::State::kDone, "done"},
27 {Command::State::kCancelled, "cancelled"},
28 {Command::State::kAborted, "aborted"},
29 {Command::State::kExpired, "expired"},
Vitaly Buka15f59092015-07-24 16:54:32 -070030};
31
Vitaly Buka0209da42015-10-08 00:07:18 -070032const EnumToStringMap<Command::Origin>::Map kMapOrigin[] = {
33 {Command::Origin::kLocal, "local"},
34 {Command::Origin::kCloud, "cloud"},
Vitaly Buka15f59092015-07-24 16:54:32 -070035};
36
Vitaly Buka47a1f6f2015-10-07 18:09:57 -070037bool ReportInvalidStateTransition(ErrorPtr* error,
Vitaly Buka0209da42015-10-08 00:07:18 -070038 Command::State from,
39 Command::State to) {
Vitaly Buka47a1f6f2015-10-07 18:09:57 -070040 Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
41 errors::commands::kInvalidState,
42 "State switch impossible: '%s' -> '%s'",
43 EnumToString(from).c_str(), EnumToString(to).c_str());
44 return false;
Vitaly Bukac6029262015-10-07 09:29:13 -070045}
46
Vitaly Buka15f59092015-07-24 16:54:32 -070047} // namespace
48
49template <>
Vitaly Buka0209da42015-10-08 00:07:18 -070050LIBWEAVE_EXPORT EnumToStringMap<Command::State>::EnumToStringMap()
Vitaly Buka15f59092015-07-24 16:54:32 -070051 : EnumToStringMap(kMapStatus) {}
52
53template <>
Vitaly Buka0209da42015-10-08 00:07:18 -070054LIBWEAVE_EXPORT EnumToStringMap<Command::Origin>::EnumToStringMap()
Vitaly Buka15f59092015-07-24 16:54:32 -070055 : EnumToStringMap(kMapOrigin) {}
Alex Vakulenkof6b38712014-09-03 16:23:38 -070056
Alex Vakulenko5ef75792015-03-19 15:50:44 -070057CommandInstance::CommandInstance(const std::string& name,
Vitaly Buka0209da42015-10-08 00:07:18 -070058 Command::Origin origin,
Alex Vakulenko36bf1b52015-11-23 09:35:37 -080059 const base::DictionaryValue& parameters)
Alex Vakulenko2c7740a2015-11-30 08:51:29 -080060 : name_{name}, origin_{origin} {
Alex Vakulenko36bf1b52015-11-23 09:35:37 -080061 parameters_.MergeDictionary(&parameters);
Alex Vakulenkoaa3a5592014-08-07 07:24:06 -070062}
63
Vitaly Bukac3d4e972015-07-21 09:55:25 -070064CommandInstance::~CommandInstance() {
Vitaly Buka157b16a2015-07-31 16:20:48 -070065 FOR_EACH_OBSERVER(Observer, observers_, OnCommandDestroyed());
Vitaly Bukac3d4e972015-07-21 09:55:25 -070066}
Anton Muhinb66a9302014-11-10 22:15:22 +040067
Vitaly Buka8d8d2192015-07-21 22:25:09 -070068const std::string& CommandInstance::GetID() const {
69 return id_;
70}
71
72const std::string& CommandInstance::GetName() const {
73 return name_;
74}
75
Alex Vakulenko88f55d82015-12-03 15:30:27 -080076const std::string& CommandInstance::GetComponent() const {
77 return component_;
78}
79
Vitaly Buka0209da42015-10-08 00:07:18 -070080Command::State CommandInstance::GetState() const {
81 return state_;
Vitaly Buka8d8d2192015-07-21 22:25:09 -070082}
83
Vitaly Buka0209da42015-10-08 00:07:18 -070084Command::Origin CommandInstance::GetOrigin() const {
Vitaly Buka8d8d2192015-07-21 22:25:09 -070085 return origin_;
86}
87
Vitaly Bukac4305602015-11-24 23:33:09 -080088const base::DictionaryValue& CommandInstance::GetParameters() const {
89 return parameters_;
Vitaly Buka8d8d2192015-07-21 22:25:09 -070090}
91
Vitaly Bukac4305602015-11-24 23:33:09 -080092const base::DictionaryValue& CommandInstance::GetProgress() const {
93 return progress_;
Vitaly Buka8d8d2192015-07-21 22:25:09 -070094}
95
Vitaly Bukac4305602015-11-24 23:33:09 -080096const base::DictionaryValue& CommandInstance::GetResults() const {
97 return results_;
Alex Vakulenkoaa3a5592014-08-07 07:24:06 -070098}
99
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700100const Error* CommandInstance::GetError() const {
101 return error_.get();
102}
103
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700104bool CommandInstance::SetProgress(const base::DictionaryValue& progress,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700105 ErrorPtr* error) {
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700106 // Change status even if progress unchanged, e.g. 0% -> 0%.
Vitaly Buka0209da42015-10-08 00:07:18 -0700107 if (!SetStatus(State::kInProgress, error))
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700108 return false;
109
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800110 if (!progress_.Equals(&progress)) {
111 progress_.Clear();
112 progress_.MergeDictionary(&progress);
Vitaly Buka157b16a2015-07-31 16:20:48 -0700113 FOR_EACH_OBSERVER(Observer, observers_, OnProgressChanged());
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700114 }
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700115
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700116 return true;
117}
118
Vitaly Buka2f548972015-10-08 19:34:49 -0700119bool CommandInstance::Complete(const base::DictionaryValue& results,
120 ErrorPtr* error) {
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800121 if (!results_.Equals(&results)) {
122 results_.Clear();
123 results_.MergeDictionary(&results);
Vitaly Buka157b16a2015-07-31 16:20:48 -0700124 FOR_EACH_OBSERVER(Observer, observers_, OnResultsChanged());
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700125 }
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700126 // Change status even if result is unchanged.
Vitaly Buka0209da42015-10-08 00:07:18 -0700127 bool result = SetStatus(State::kDone, error);
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700128 RemoveFromQueue();
129 // The command will be destroyed after that, so do not access any members.
130 return result;
131}
132
133bool CommandInstance::SetError(const Error* command_error, ErrorPtr* error) {
134 error_ = command_error ? command_error->Clone() : nullptr;
Vitaly Buka375f3282015-10-07 18:34:15 -0700135 FOR_EACH_OBSERVER(Observer, observers_, OnErrorChanged());
Vitaly Buka0209da42015-10-08 00:07:18 -0700136 return SetStatus(State::kError, error);
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700137}
138
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700139namespace {
140
141// Helper method to retrieve command parameters from the command definition
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800142// object passed in as |json|.
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700143// On success, returns |true| and the validated parameters and values through
144// |parameters|. Otherwise returns |false| and additional error information in
145// |error|.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800146std::unique_ptr<base::DictionaryValue> GetCommandParameters(
147 const base::DictionaryValue* json,
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800148 ErrorPtr* error) {
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700149 // Get the command parameters from 'parameters' property.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800150 std::unique_ptr<base::DictionaryValue> params;
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700151 const base::Value* params_value = nullptr;
Alex Vakulenkof784e212015-04-20 12:33:52 -0700152 if (json->Get(commands::attributes::kCommand_Parameters, &params_value)) {
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700153 // Make sure the "parameters" property is actually an object.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800154 const base::DictionaryValue* params_dict = nullptr;
155 if (!params_value->GetAsDictionary(&params_dict)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700156 Error::AddToPrintf(error, FROM_HERE, errors::json::kDomain,
157 errors::json::kObjectExpected,
158 "Property '%s' must be a JSON object",
159 commands::attributes::kCommand_Parameters);
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800160 return params;
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700161 }
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800162 params.reset(params_dict->DeepCopy());
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700163 } else {
164 // "parameters" are not specified. Assume empty param list.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800165 params.reset(new base::DictionaryValue);
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700166 }
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800167 return params;
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700168}
169
170} // anonymous namespace
171
Alex Vakulenkofedc4872014-08-20 12:38:43 -0700172std::unique_ptr<CommandInstance> CommandInstance::FromJson(
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700173 const base::Value* value,
Vitaly Buka0209da42015-10-08 00:07:18 -0700174 Command::Origin origin,
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700175 std::string* command_id,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700176 ErrorPtr* error) {
Alex Vakulenkofedc4872014-08-20 12:38:43 -0700177 std::unique_ptr<CommandInstance> instance;
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700178 std::string command_id_buffer; // used if |command_id| was nullptr.
179 if (!command_id)
180 command_id = &command_id_buffer;
181
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700182 // Get the command JSON object from the value.
183 const base::DictionaryValue* json = nullptr;
184 if (!value->GetAsDictionary(&json)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700185 Error::AddTo(error, FROM_HERE, errors::json::kDomain,
186 errors::json::kObjectExpected,
187 "Command instance is not a JSON object");
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700188 command_id->clear();
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700189 return instance;
190 }
191
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700192 // Get the command ID from 'id' property.
193 if (!json->GetString(commands::attributes::kCommand_Id, command_id))
194 command_id->clear();
195
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700196 // Get the command name from 'name' property.
197 std::string command_name;
Alex Vakulenkof784e212015-04-20 12:33:52 -0700198 if (!json->GetString(commands::attributes::kCommand_Name, &command_name)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700199 Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
200 errors::commands::kPropertyMissing, "Command name is missing");
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700201 return instance;
202 }
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700203
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800204 auto parameters = GetCommandParameters(json, error);
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800205 if (!parameters) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700206 Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
207 errors::commands::kCommandFailed,
208 "Failed to validate command '%s'", command_name.c_str());
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700209 return instance;
210 }
211
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800212 instance.reset(new CommandInstance{command_name, origin, *parameters});
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700213
214 if (!command_id->empty())
215 instance->SetID(*command_id);
Anton Muhin5191e812014-10-30 17:49:48 +0400216
Alex Vakulenko88f55d82015-12-03 15:30:27 -0800217 // Get the component name this command is for.
218 std::string component;
219 if (json->GetString(commands::attributes::kCommand_Component, &component))
220 instance->SetComponent(component);
221
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700222 return instance;
223}
224
Vitaly Buka906d39e2015-03-24 10:08:26 -0700225std::unique_ptr<base::DictionaryValue> CommandInstance::ToJson() const {
226 std::unique_ptr<base::DictionaryValue> json{new base::DictionaryValue};
227
228 json->SetString(commands::attributes::kCommand_Id, id_);
229 json->SetString(commands::attributes::kCommand_Name, name_);
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800230 json->Set(commands::attributes::kCommand_Parameters, parameters_.DeepCopy());
231 json->Set(commands::attributes::kCommand_Progress, progress_.DeepCopy());
232 json->Set(commands::attributes::kCommand_Results, results_.DeepCopy());
Vitaly Buka0209da42015-10-08 00:07:18 -0700233 json->SetString(commands::attributes::kCommand_State, EnumToString(state_));
Vitaly Buka70f77d92015-10-07 15:42:40 -0700234 if (error_) {
235 json->Set(commands::attributes::kCommand_Error,
236 ErrorInfoToJson(*error_).release());
237 }
Vitaly Buka906d39e2015-03-24 10:08:26 -0700238
239 return json;
240}
241
Vitaly Buka67b53552015-07-21 10:36:56 -0700242void CommandInstance::AddObserver(Observer* observer) {
Vitaly Buka157b16a2015-07-31 16:20:48 -0700243 observers_.AddObserver(observer);
244}
245
246void CommandInstance::RemoveObserver(Observer* observer) {
247 observers_.RemoveObserver(observer);
Anton Muhinb66a9302014-11-10 22:15:22 +0400248}
249
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700250bool CommandInstance::Pause(ErrorPtr* error) {
Vitaly Buka0209da42015-10-08 00:07:18 -0700251 return SetStatus(State::kPaused, error);
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700252}
253
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700254bool CommandInstance::Abort(const Error* command_error, ErrorPtr* error) {
255 error_ = command_error ? command_error->Clone() : nullptr;
Vitaly Buka375f3282015-10-07 18:34:15 -0700256 FOR_EACH_OBSERVER(Observer, observers_, OnErrorChanged());
Vitaly Buka0209da42015-10-08 00:07:18 -0700257 bool result = SetStatus(State::kAborted, error);
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700258 RemoveFromQueue();
259 // The command will be destroyed after that, so do not access any members.
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700260 return result;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700261}
262
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700263bool CommandInstance::Cancel(ErrorPtr* error) {
Vitaly Buka0209da42015-10-08 00:07:18 -0700264 bool result = SetStatus(State::kCancelled, error);
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700265 RemoveFromQueue();
266 // The command will be destroyed after that, so do not access any members.
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700267 return result;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700268}
269
Vitaly Buka0209da42015-10-08 00:07:18 -0700270bool CommandInstance::SetStatus(Command::State status, ErrorPtr* error) {
271 if (status == state_)
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700272 return true;
Vitaly Buka0209da42015-10-08 00:07:18 -0700273 if (status == State::kQueued)
274 return ReportInvalidStateTransition(error, state_, status);
275 switch (state_) {
276 case State::kDone:
277 case State::kCancelled:
278 case State::kAborted:
279 case State::kExpired:
280 return ReportInvalidStateTransition(error, state_, status);
281 case State::kQueued:
282 case State::kInProgress:
283 case State::kPaused:
284 case State::kError:
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700285 break;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700286 }
Vitaly Buka0209da42015-10-08 00:07:18 -0700287 state_ = status;
288 FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700289 return true;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700290}
291
292void CommandInstance::RemoveFromQueue() {
Vitaly Buka2a9b30f2015-04-01 10:51:59 -0700293 if (queue_)
294 queue_->DelayedRemove(GetID());
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700295}
296
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700297} // namespace weave