blob: 1f2e4a265aa66d9c1a5027a8a1497d56e7006f0f [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_dictionary.h"
13#include "src/commands/command_queue.h"
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020014#include "src/commands/schema_constants.h"
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020015#include "src/json_error_codes.h"
Vitaly Buka70f77d92015-10-07 15:42:40 -070016#include "src/utils.h"
Alex Vakulenko8dc69af2014-08-07 10:29:42 -070017
Vitaly Bukab6f015a2015-07-09 14:59:23 -070018namespace weave {
Alex Vakulenkoaa3a5592014-08-07 07:24:06 -070019
Vitaly Buka15f59092015-07-24 16:54:32 -070020namespace {
21
Vitaly Buka0209da42015-10-08 00:07:18 -070022const EnumToStringMap<Command::State>::Map kMapStatus[] = {
23 {Command::State::kQueued, "queued"},
24 {Command::State::kInProgress, "inProgress"},
25 {Command::State::kPaused, "paused"},
26 {Command::State::kError, "error"},
27 {Command::State::kDone, "done"},
28 {Command::State::kCancelled, "cancelled"},
29 {Command::State::kAborted, "aborted"},
30 {Command::State::kExpired, "expired"},
Vitaly Buka15f59092015-07-24 16:54:32 -070031};
32
Vitaly Buka0209da42015-10-08 00:07:18 -070033const EnumToStringMap<Command::Origin>::Map kMapOrigin[] = {
34 {Command::Origin::kLocal, "local"},
35 {Command::Origin::kCloud, "cloud"},
Vitaly Buka15f59092015-07-24 16:54:32 -070036};
37
Vitaly Buka47a1f6f2015-10-07 18:09:57 -070038bool ReportInvalidStateTransition(ErrorPtr* error,
Vitaly Buka0209da42015-10-08 00:07:18 -070039 Command::State from,
40 Command::State to) {
Vitaly Buka47a1f6f2015-10-07 18:09:57 -070041 Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
42 errors::commands::kInvalidState,
43 "State switch impossible: '%s' -> '%s'",
44 EnumToString(from).c_str(), EnumToString(to).c_str());
45 return false;
Vitaly Bukac6029262015-10-07 09:29:13 -070046}
47
Vitaly Buka15f59092015-07-24 16:54:32 -070048} // namespace
49
50template <>
Vitaly Buka0209da42015-10-08 00:07:18 -070051LIBWEAVE_EXPORT EnumToStringMap<Command::State>::EnumToStringMap()
Vitaly Buka15f59092015-07-24 16:54:32 -070052 : EnumToStringMap(kMapStatus) {}
53
54template <>
Vitaly Buka0209da42015-10-08 00:07:18 -070055LIBWEAVE_EXPORT EnumToStringMap<Command::Origin>::EnumToStringMap()
Vitaly Buka15f59092015-07-24 16:54:32 -070056 : EnumToStringMap(kMapOrigin) {}
Alex Vakulenkof6b38712014-09-03 16:23:38 -070057
Alex Vakulenko5ef75792015-03-19 15:50:44 -070058CommandInstance::CommandInstance(const std::string& name,
Vitaly Buka0209da42015-10-08 00:07:18 -070059 Command::Origin origin,
Alex Vakulenko36bf1b52015-11-23 09:35:37 -080060 const base::DictionaryValue& parameters)
Alex Vakulenko2c7740a2015-11-30 08:51:29 -080061 : name_{name}, origin_{origin} {
Alex Vakulenko36bf1b52015-11-23 09:35:37 -080062 parameters_.MergeDictionary(&parameters);
Alex Vakulenkoaa3a5592014-08-07 07:24:06 -070063}
64
Vitaly Bukac3d4e972015-07-21 09:55:25 -070065CommandInstance::~CommandInstance() {
Vitaly Buka157b16a2015-07-31 16:20:48 -070066 FOR_EACH_OBSERVER(Observer, observers_, OnCommandDestroyed());
Vitaly Bukac3d4e972015-07-21 09:55:25 -070067}
Anton Muhinb66a9302014-11-10 22:15:22 +040068
Vitaly Buka8d8d2192015-07-21 22:25:09 -070069const std::string& CommandInstance::GetID() const {
70 return id_;
71}
72
73const std::string& CommandInstance::GetName() const {
74 return name_;
75}
76
Vitaly Buka0209da42015-10-08 00:07:18 -070077Command::State CommandInstance::GetState() const {
78 return state_;
Vitaly Buka8d8d2192015-07-21 22:25:09 -070079}
80
Vitaly Buka0209da42015-10-08 00:07:18 -070081Command::Origin CommandInstance::GetOrigin() const {
Vitaly Buka8d8d2192015-07-21 22:25:09 -070082 return origin_;
83}
84
Vitaly Bukac4305602015-11-24 23:33:09 -080085const base::DictionaryValue& CommandInstance::GetParameters() const {
86 return parameters_;
Vitaly Buka8d8d2192015-07-21 22:25:09 -070087}
88
Vitaly Bukac4305602015-11-24 23:33:09 -080089const base::DictionaryValue& CommandInstance::GetProgress() const {
90 return progress_;
Vitaly Buka8d8d2192015-07-21 22:25:09 -070091}
92
Vitaly Bukac4305602015-11-24 23:33:09 -080093const base::DictionaryValue& CommandInstance::GetResults() const {
94 return results_;
Alex Vakulenkoaa3a5592014-08-07 07:24:06 -070095}
96
Vitaly Buka47a1f6f2015-10-07 18:09:57 -070097const Error* CommandInstance::GetError() const {
98 return error_.get();
99}
100
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700101bool CommandInstance::SetProgress(const base::DictionaryValue& progress,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700102 ErrorPtr* error) {
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700103 // Change status even if progress unchanged, e.g. 0% -> 0%.
Vitaly Buka0209da42015-10-08 00:07:18 -0700104 if (!SetStatus(State::kInProgress, error))
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700105 return false;
106
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800107 if (!progress_.Equals(&progress)) {
108 progress_.Clear();
109 progress_.MergeDictionary(&progress);
Vitaly Buka157b16a2015-07-31 16:20:48 -0700110 FOR_EACH_OBSERVER(Observer, observers_, OnProgressChanged());
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700111 }
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700112
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700113 return true;
114}
115
Vitaly Buka2f548972015-10-08 19:34:49 -0700116bool CommandInstance::Complete(const base::DictionaryValue& results,
117 ErrorPtr* error) {
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800118 if (!results_.Equals(&results)) {
119 results_.Clear();
120 results_.MergeDictionary(&results);
Vitaly Buka157b16a2015-07-31 16:20:48 -0700121 FOR_EACH_OBSERVER(Observer, observers_, OnResultsChanged());
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700122 }
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700123 // Change status even if result is unchanged.
Vitaly Buka0209da42015-10-08 00:07:18 -0700124 bool result = SetStatus(State::kDone, error);
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700125 RemoveFromQueue();
126 // The command will be destroyed after that, so do not access any members.
127 return result;
128}
129
130bool CommandInstance::SetError(const Error* command_error, ErrorPtr* error) {
131 error_ = command_error ? command_error->Clone() : nullptr;
Vitaly Buka375f3282015-10-07 18:34:15 -0700132 FOR_EACH_OBSERVER(Observer, observers_, OnErrorChanged());
Vitaly Buka0209da42015-10-08 00:07:18 -0700133 return SetStatus(State::kError, error);
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700134}
135
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700136namespace {
137
138// Helper method to retrieve command parameters from the command definition
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800139// object passed in as |json|.
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700140// On success, returns |true| and the validated parameters and values through
141// |parameters|. Otherwise returns |false| and additional error information in
142// |error|.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800143std::unique_ptr<base::DictionaryValue> GetCommandParameters(
144 const base::DictionaryValue* json,
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800145 ErrorPtr* error) {
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700146 // Get the command parameters from 'parameters' property.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800147 std::unique_ptr<base::DictionaryValue> params;
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700148 const base::Value* params_value = nullptr;
Alex Vakulenkof784e212015-04-20 12:33:52 -0700149 if (json->Get(commands::attributes::kCommand_Parameters, &params_value)) {
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700150 // Make sure the "parameters" property is actually an object.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800151 const base::DictionaryValue* params_dict = nullptr;
152 if (!params_value->GetAsDictionary(&params_dict)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700153 Error::AddToPrintf(error, FROM_HERE, errors::json::kDomain,
154 errors::json::kObjectExpected,
155 "Property '%s' must be a JSON object",
156 commands::attributes::kCommand_Parameters);
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800157 return params;
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700158 }
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800159 params.reset(params_dict->DeepCopy());
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700160 } else {
161 // "parameters" are not specified. Assume empty param list.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800162 params.reset(new base::DictionaryValue);
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700163 }
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800164 return params;
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700165}
166
167} // anonymous namespace
168
Alex Vakulenkofedc4872014-08-20 12:38:43 -0700169std::unique_ptr<CommandInstance> CommandInstance::FromJson(
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700170 const base::Value* value,
Vitaly Buka0209da42015-10-08 00:07:18 -0700171 Command::Origin origin,
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700172 const CommandDictionary& dictionary,
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700173 std::string* command_id,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700174 ErrorPtr* error) {
Alex Vakulenkofedc4872014-08-20 12:38:43 -0700175 std::unique_ptr<CommandInstance> instance;
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700176 std::string command_id_buffer; // used if |command_id| was nullptr.
177 if (!command_id)
178 command_id = &command_id_buffer;
179
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700180 // Get the command JSON object from the value.
181 const base::DictionaryValue* json = nullptr;
182 if (!value->GetAsDictionary(&json)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700183 Error::AddTo(error, FROM_HERE, errors::json::kDomain,
184 errors::json::kObjectExpected,
185 "Command instance is not a JSON object");
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700186 command_id->clear();
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700187 return instance;
188 }
189
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700190 // Get the command ID from 'id' property.
191 if (!json->GetString(commands::attributes::kCommand_Id, command_id))
192 command_id->clear();
193
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700194 // Get the command name from 'name' property.
195 std::string command_name;
Alex Vakulenkof784e212015-04-20 12:33:52 -0700196 if (!json->GetString(commands::attributes::kCommand_Name, &command_name)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700197 Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
198 errors::commands::kPropertyMissing, "Command name is missing");
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700199 return instance;
200 }
201 // Make sure we know how to handle the command with this name.
Anton Muhincfde8692014-11-25 03:36:59 +0400202 auto command_def = dictionary.FindCommand(command_name);
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700203 if (!command_def) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700204 Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
205 errors::commands::kInvalidCommandName,
206 "Unknown command received: %s", command_name.c_str());
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700207 return instance;
208 }
209
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800210 auto parameters = GetCommandParameters(json, error);
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800211 if (!parameters) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700212 Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
213 errors::commands::kCommandFailed,
214 "Failed to validate command '%s'", command_name.c_str());
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700215 return instance;
216 }
217
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800218 instance.reset(new CommandInstance{command_name, origin, *parameters});
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700219
220 if (!command_id->empty())
221 instance->SetID(*command_id);
Anton Muhin5191e812014-10-30 17:49:48 +0400222
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700223 return instance;
224}
225
Vitaly Buka906d39e2015-03-24 10:08:26 -0700226std::unique_ptr<base::DictionaryValue> CommandInstance::ToJson() const {
227 std::unique_ptr<base::DictionaryValue> json{new base::DictionaryValue};
228
229 json->SetString(commands::attributes::kCommand_Id, id_);
230 json->SetString(commands::attributes::kCommand_Name, name_);
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800231 json->Set(commands::attributes::kCommand_Parameters, parameters_.DeepCopy());
232 json->Set(commands::attributes::kCommand_Progress, progress_.DeepCopy());
233 json->Set(commands::attributes::kCommand_Results, results_.DeepCopy());
Vitaly Buka0209da42015-10-08 00:07:18 -0700234 json->SetString(commands::attributes::kCommand_State, EnumToString(state_));
Vitaly Buka70f77d92015-10-07 15:42:40 -0700235 if (error_) {
236 json->Set(commands::attributes::kCommand_Error,
237 ErrorInfoToJson(*error_).release());
238 }
Vitaly Buka906d39e2015-03-24 10:08:26 -0700239
240 return json;
241}
242
Vitaly Buka67b53552015-07-21 10:36:56 -0700243void CommandInstance::AddObserver(Observer* observer) {
Vitaly Buka157b16a2015-07-31 16:20:48 -0700244 observers_.AddObserver(observer);
245}
246
247void CommandInstance::RemoveObserver(Observer* observer) {
248 observers_.RemoveObserver(observer);
Anton Muhinb66a9302014-11-10 22:15:22 +0400249}
250
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700251bool CommandInstance::Pause(ErrorPtr* error) {
Vitaly Buka0209da42015-10-08 00:07:18 -0700252 return SetStatus(State::kPaused, error);
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700253}
254
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700255bool CommandInstance::Abort(const Error* command_error, ErrorPtr* error) {
256 error_ = command_error ? command_error->Clone() : nullptr;
Vitaly Buka375f3282015-10-07 18:34:15 -0700257 FOR_EACH_OBSERVER(Observer, observers_, OnErrorChanged());
Vitaly Buka0209da42015-10-08 00:07:18 -0700258 bool result = SetStatus(State::kAborted, error);
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700259 RemoveFromQueue();
260 // The command will be destroyed after that, so do not access any members.
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700261 return result;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700262}
263
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700264bool CommandInstance::Cancel(ErrorPtr* error) {
Vitaly Buka0209da42015-10-08 00:07:18 -0700265 bool result = SetStatus(State::kCancelled, error);
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700266 RemoveFromQueue();
267 // The command will be destroyed after that, so do not access any members.
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700268 return result;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700269}
270
Vitaly Buka0209da42015-10-08 00:07:18 -0700271bool CommandInstance::SetStatus(Command::State status, ErrorPtr* error) {
272 if (status == state_)
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700273 return true;
Vitaly Buka0209da42015-10-08 00:07:18 -0700274 if (status == State::kQueued)
275 return ReportInvalidStateTransition(error, state_, status);
276 switch (state_) {
277 case State::kDone:
278 case State::kCancelled:
279 case State::kAborted:
280 case State::kExpired:
281 return ReportInvalidStateTransition(error, state_, status);
282 case State::kQueued:
283 case State::kInProgress:
284 case State::kPaused:
285 case State::kError:
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700286 break;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700287 }
Vitaly Buka0209da42015-10-08 00:07:18 -0700288 state_ = status;
289 FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700290 return true;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700291}
292
293void CommandInstance::RemoveFromQueue() {
Vitaly Buka2a9b30f2015-04-01 10:51:59 -0700294 if (queue_)
295 queue_->DelayedRemove(GetID());
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700296}
297
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700298} // namespace weave