blob: dfc3fbdc823ea0d501981da70d5e17e05c96d290 [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 Buka0dbbf602016-01-22 11:38:37 -080040 return Error::AddToPrintf(error, FROM_HERE, errors::commands::kInvalidState,
41 "State switch impossible: '%s' -> '%s'",
42 EnumToString(from).c_str(),
43 EnumToString(to).c_str());
Vitaly Bukac6029262015-10-07 09:29:13 -070044}
45
Vitaly Buka15f59092015-07-24 16:54:32 -070046} // namespace
47
48template <>
Vitaly Buka0209da42015-10-08 00:07:18 -070049LIBWEAVE_EXPORT EnumToStringMap<Command::State>::EnumToStringMap()
Vitaly Buka15f59092015-07-24 16:54:32 -070050 : EnumToStringMap(kMapStatus) {}
51
52template <>
Vitaly Buka0209da42015-10-08 00:07:18 -070053LIBWEAVE_EXPORT EnumToStringMap<Command::Origin>::EnumToStringMap()
Vitaly Buka15f59092015-07-24 16:54:32 -070054 : EnumToStringMap(kMapOrigin) {}
Alex Vakulenkof6b38712014-09-03 16:23:38 -070055
Alex Vakulenko5ef75792015-03-19 15:50:44 -070056CommandInstance::CommandInstance(const std::string& name,
Vitaly Buka0209da42015-10-08 00:07:18 -070057 Command::Origin origin,
Alex Vakulenko36bf1b52015-11-23 09:35:37 -080058 const base::DictionaryValue& parameters)
Alex Vakulenko2c7740a2015-11-30 08:51:29 -080059 : name_{name}, origin_{origin} {
Alex Vakulenko36bf1b52015-11-23 09:35:37 -080060 parameters_.MergeDictionary(&parameters);
Alex Vakulenkoaa3a5592014-08-07 07:24:06 -070061}
62
Vitaly Bukac3d4e972015-07-21 09:55:25 -070063CommandInstance::~CommandInstance() {
Vitaly Buka157b16a2015-07-31 16:20:48 -070064 FOR_EACH_OBSERVER(Observer, observers_, OnCommandDestroyed());
Vitaly Bukac3d4e972015-07-21 09:55:25 -070065}
Anton Muhinb66a9302014-11-10 22:15:22 +040066
Vitaly Buka8d8d2192015-07-21 22:25:09 -070067const std::string& CommandInstance::GetID() const {
68 return id_;
69}
70
71const std::string& CommandInstance::GetName() const {
72 return name_;
73}
74
Alex Vakulenko88f55d82015-12-03 15:30:27 -080075const std::string& CommandInstance::GetComponent() const {
76 return component_;
77}
78
Vitaly Buka0209da42015-10-08 00:07:18 -070079Command::State CommandInstance::GetState() const {
80 return state_;
Vitaly Buka8d8d2192015-07-21 22:25:09 -070081}
82
Vitaly Buka0209da42015-10-08 00:07:18 -070083Command::Origin CommandInstance::GetOrigin() const {
Vitaly Buka8d8d2192015-07-21 22:25:09 -070084 return origin_;
85}
86
Vitaly Bukac4305602015-11-24 23:33:09 -080087const base::DictionaryValue& CommandInstance::GetParameters() const {
88 return parameters_;
Vitaly Buka8d8d2192015-07-21 22:25:09 -070089}
90
Vitaly Bukac4305602015-11-24 23:33:09 -080091const base::DictionaryValue& CommandInstance::GetProgress() const {
92 return progress_;
Vitaly Buka8d8d2192015-07-21 22:25:09 -070093}
94
Vitaly Bukac4305602015-11-24 23:33:09 -080095const base::DictionaryValue& CommandInstance::GetResults() const {
96 return results_;
Alex Vakulenkoaa3a5592014-08-07 07:24:06 -070097}
98
Vitaly Buka47a1f6f2015-10-07 18:09:57 -070099const Error* CommandInstance::GetError() const {
100 return error_.get();
101}
102
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700103bool CommandInstance::SetProgress(const base::DictionaryValue& progress,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700104 ErrorPtr* error) {
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700105 // Change status even if progress unchanged, e.g. 0% -> 0%.
Vitaly Buka0209da42015-10-08 00:07:18 -0700106 if (!SetStatus(State::kInProgress, error))
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700107 return false;
108
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800109 if (!progress_.Equals(&progress)) {
110 progress_.Clear();
111 progress_.MergeDictionary(&progress);
Vitaly Buka157b16a2015-07-31 16:20:48 -0700112 FOR_EACH_OBSERVER(Observer, observers_, OnProgressChanged());
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700113 }
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700114
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700115 return true;
116}
117
Vitaly Buka2f548972015-10-08 19:34:49 -0700118bool CommandInstance::Complete(const base::DictionaryValue& results,
119 ErrorPtr* error) {
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800120 if (!results_.Equals(&results)) {
121 results_.Clear();
122 results_.MergeDictionary(&results);
Vitaly Buka157b16a2015-07-31 16:20:48 -0700123 FOR_EACH_OBSERVER(Observer, observers_, OnResultsChanged());
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700124 }
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700125 // Change status even if result is unchanged.
Vitaly Buka0209da42015-10-08 00:07:18 -0700126 bool result = SetStatus(State::kDone, error);
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700127 RemoveFromQueue();
128 // The command will be destroyed after that, so do not access any members.
129 return result;
130}
131
132bool CommandInstance::SetError(const Error* command_error, ErrorPtr* error) {
133 error_ = command_error ? command_error->Clone() : nullptr;
Vitaly Buka375f3282015-10-07 18:34:15 -0700134 FOR_EACH_OBSERVER(Observer, observers_, OnErrorChanged());
Vitaly Buka0209da42015-10-08 00:07:18 -0700135 return SetStatus(State::kError, error);
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700136}
137
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700138namespace {
139
140// Helper method to retrieve command parameters from the command definition
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800141// object passed in as |json|.
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700142// On success, returns |true| and the validated parameters and values through
143// |parameters|. Otherwise returns |false| and additional error information in
144// |error|.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800145std::unique_ptr<base::DictionaryValue> GetCommandParameters(
146 const base::DictionaryValue* json,
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800147 ErrorPtr* error) {
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700148 // Get the command parameters from 'parameters' property.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800149 std::unique_ptr<base::DictionaryValue> params;
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700150 const base::Value* params_value = nullptr;
Alex Vakulenkof784e212015-04-20 12:33:52 -0700151 if (json->Get(commands::attributes::kCommand_Parameters, &params_value)) {
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700152 // Make sure the "parameters" property is actually an object.
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800153 const base::DictionaryValue* params_dict = nullptr;
154 if (!params_value->GetAsDictionary(&params_dict)) {
Vitaly Buka0dbbf602016-01-22 11:38:37 -0800155 return Error::AddToPrintf(error, FROM_HERE, errors::json::kObjectExpected,
156 "Property '%s' must be a JSON object",
157 commands::attributes::kCommand_Parameters);
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 Vakulenkod1978d32015-04-29 17:33:26 -0700172 std::string* command_id,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700173 ErrorPtr* error) {
Alex Vakulenkofedc4872014-08-20 12:38:43 -0700174 std::unique_ptr<CommandInstance> instance;
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700175 std::string command_id_buffer; // used if |command_id| was nullptr.
176 if (!command_id)
177 command_id = &command_id_buffer;
178
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700179 // Get the command JSON object from the value.
180 const base::DictionaryValue* json = nullptr;
181 if (!value->GetAsDictionary(&json)) {
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700182 command_id->clear();
Vitaly Buka0dbbf602016-01-22 11:38:37 -0800183 return Error::AddTo(error, FROM_HERE, errors::json::kObjectExpected,
184 "Command instance is not a JSON object");
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700185 }
186
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700187 // Get the command ID from 'id' property.
188 if (!json->GetString(commands::attributes::kCommand_Id, command_id))
189 command_id->clear();
190
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700191 // Get the command name from 'name' property.
192 std::string command_name;
Alex Vakulenkof784e212015-04-20 12:33:52 -0700193 if (!json->GetString(commands::attributes::kCommand_Name, &command_name)) {
Vitaly Buka0dbbf602016-01-22 11:38:37 -0800194 return Error::AddTo(error, FROM_HERE, errors::commands::kPropertyMissing,
195 "Command name is missing");
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700196 }
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700197
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800198 auto parameters = GetCommandParameters(json, error);
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800199 if (!parameters) {
Vitaly Buka0dbbf602016-01-22 11:38:37 -0800200 return Error::AddToPrintf(
201 error, FROM_HERE, errors::commands::kCommandFailed,
202 "Failed to validate command '%s'", command_name.c_str());
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700203 }
204
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800205 instance.reset(new CommandInstance{command_name, origin, *parameters});
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700206
207 if (!command_id->empty())
208 instance->SetID(*command_id);
Anton Muhin5191e812014-10-30 17:49:48 +0400209
Alex Vakulenko88f55d82015-12-03 15:30:27 -0800210 // Get the component name this command is for.
211 std::string component;
212 if (json->GetString(commands::attributes::kCommand_Component, &component))
213 instance->SetComponent(component);
214
Alex Vakulenko8dc69af2014-08-07 10:29:42 -0700215 return instance;
216}
217
Vitaly Buka906d39e2015-03-24 10:08:26 -0700218std::unique_ptr<base::DictionaryValue> CommandInstance::ToJson() const {
219 std::unique_ptr<base::DictionaryValue> json{new base::DictionaryValue};
220
221 json->SetString(commands::attributes::kCommand_Id, id_);
222 json->SetString(commands::attributes::kCommand_Name, name_);
Vitaly Buka5ddd9912016-03-09 10:44:48 -0800223 json->SetString(commands::attributes::kCommand_Component, component_);
Alex Vakulenko36bf1b52015-11-23 09:35:37 -0800224 json->Set(commands::attributes::kCommand_Parameters, parameters_.DeepCopy());
225 json->Set(commands::attributes::kCommand_Progress, progress_.DeepCopy());
226 json->Set(commands::attributes::kCommand_Results, results_.DeepCopy());
Vitaly Buka0209da42015-10-08 00:07:18 -0700227 json->SetString(commands::attributes::kCommand_State, EnumToString(state_));
Vitaly Buka70f77d92015-10-07 15:42:40 -0700228 if (error_) {
229 json->Set(commands::attributes::kCommand_Error,
230 ErrorInfoToJson(*error_).release());
231 }
Vitaly Buka906d39e2015-03-24 10:08:26 -0700232
233 return json;
234}
235
Vitaly Buka67b53552015-07-21 10:36:56 -0700236void CommandInstance::AddObserver(Observer* observer) {
Vitaly Buka157b16a2015-07-31 16:20:48 -0700237 observers_.AddObserver(observer);
238}
239
240void CommandInstance::RemoveObserver(Observer* observer) {
241 observers_.RemoveObserver(observer);
Anton Muhinb66a9302014-11-10 22:15:22 +0400242}
243
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700244bool CommandInstance::Pause(ErrorPtr* error) {
Vitaly Buka0209da42015-10-08 00:07:18 -0700245 return SetStatus(State::kPaused, error);
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700246}
247
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700248bool CommandInstance::Abort(const Error* command_error, ErrorPtr* error) {
249 error_ = command_error ? command_error->Clone() : nullptr;
Vitaly Buka375f3282015-10-07 18:34:15 -0700250 FOR_EACH_OBSERVER(Observer, observers_, OnErrorChanged());
Vitaly Buka0209da42015-10-08 00:07:18 -0700251 bool result = SetStatus(State::kAborted, error);
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700252 RemoveFromQueue();
253 // The command will be destroyed after that, so do not access any members.
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700254 return result;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700255}
256
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700257bool CommandInstance::Cancel(ErrorPtr* error) {
Vitaly Buka0209da42015-10-08 00:07:18 -0700258 bool result = SetStatus(State::kCancelled, 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 Buka0209da42015-10-08 00:07:18 -0700264bool CommandInstance::SetStatus(Command::State status, ErrorPtr* error) {
265 if (status == state_)
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700266 return true;
Vitaly Buka0209da42015-10-08 00:07:18 -0700267 if (status == State::kQueued)
268 return ReportInvalidStateTransition(error, state_, status);
269 switch (state_) {
270 case State::kDone:
271 case State::kCancelled:
272 case State::kAborted:
273 case State::kExpired:
274 return ReportInvalidStateTransition(error, state_, status);
275 case State::kQueued:
276 case State::kInProgress:
277 case State::kPaused:
278 case State::kError:
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700279 break;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700280 }
Vitaly Buka0209da42015-10-08 00:07:18 -0700281 state_ = status;
282 FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700283 return true;
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700284}
285
286void CommandInstance::RemoveFromQueue() {
Vitaly Buka2a9b30f2015-04-01 10:51:59 -0700287 if (queue_)
Alex Vakulenko329ad802016-02-01 12:11:30 -0800288 queue_->RemoveLater(GetID());
Alex Vakulenkof6b38712014-09-03 16:23:38 -0700289}
290
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700291} // namespace weave