1 // Copyright 2015 The Weave Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/commands/command_instance.h"
6
7 #include <base/values.h>
8 #include <weave/enum_to_string.h>
9 #include <weave/error.h>
10 #include <weave/export.h>
11
12 #include "src/commands/command_queue.h"
13 #include "src/commands/schema_constants.h"
14 #include "src/json_error_codes.h"
15 #include "src/utils.h"
16
17 namespace weave {
18
19 namespace {
20
21 const 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"},
30 };
31
32 const EnumToStringMap<Command::Origin>::Map kMapOrigin[] = {
33 {Command::Origin::kLocal, "local"},
34 {Command::Origin::kCloud, "cloud"},
35 };
36
ReportInvalidStateTransition(ErrorPtr * error,Command::State from,Command::State to)37 bool ReportInvalidStateTransition(ErrorPtr* error,
38 Command::State from,
39 Command::State to) {
40 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());
44 }
45
46 } // namespace
47
48 template <>
EnumToStringMap()49 LIBWEAVE_EXPORT EnumToStringMap<Command::State>::EnumToStringMap()
50 : EnumToStringMap(kMapStatus) {}
51
52 template <>
EnumToStringMap()53 LIBWEAVE_EXPORT EnumToStringMap<Command::Origin>::EnumToStringMap()
54 : EnumToStringMap(kMapOrigin) {}
55
CommandInstance(const std::string & name,Command::Origin origin,const base::DictionaryValue & parameters)56 CommandInstance::CommandInstance(const std::string& name,
57 Command::Origin origin,
58 const base::DictionaryValue& parameters)
59 : name_{name}, origin_{origin} {
60 parameters_.MergeDictionary(¶meters);
61 }
62
~CommandInstance()63 CommandInstance::~CommandInstance() {
64 FOR_EACH_OBSERVER(Observer, observers_, OnCommandDestroyed());
65 }
66
GetID() const67 const std::string& CommandInstance::GetID() const {
68 return id_;
69 }
70
GetName() const71 const std::string& CommandInstance::GetName() const {
72 return name_;
73 }
74
GetComponent() const75 const std::string& CommandInstance::GetComponent() const {
76 return component_;
77 }
78
GetState() const79 Command::State CommandInstance::GetState() const {
80 return state_;
81 }
82
GetOrigin() const83 Command::Origin CommandInstance::GetOrigin() const {
84 return origin_;
85 }
86
GetParameters() const87 const base::DictionaryValue& CommandInstance::GetParameters() const {
88 return parameters_;
89 }
90
GetProgress() const91 const base::DictionaryValue& CommandInstance::GetProgress() const {
92 return progress_;
93 }
94
GetResults() const95 const base::DictionaryValue& CommandInstance::GetResults() const {
96 return results_;
97 }
98
GetError() const99 const Error* CommandInstance::GetError() const {
100 return error_.get();
101 }
102
SetProgress(const base::DictionaryValue & progress,ErrorPtr * error)103 bool CommandInstance::SetProgress(const base::DictionaryValue& progress,
104 ErrorPtr* error) {
105 // Change status even if progress unchanged, e.g. 0% -> 0%.
106 if (!SetStatus(State::kInProgress, error))
107 return false;
108
109 if (!progress_.Equals(&progress)) {
110 progress_.Clear();
111 progress_.MergeDictionary(&progress);
112 FOR_EACH_OBSERVER(Observer, observers_, OnProgressChanged());
113 }
114
115 return true;
116 }
117
Complete(const base::DictionaryValue & results,ErrorPtr * error)118 bool CommandInstance::Complete(const base::DictionaryValue& results,
119 ErrorPtr* error) {
120 if (!results_.Equals(&results)) {
121 results_.Clear();
122 results_.MergeDictionary(&results);
123 FOR_EACH_OBSERVER(Observer, observers_, OnResultsChanged());
124 }
125 // Change status even if result is unchanged.
126 bool result = SetStatus(State::kDone, error);
127 RemoveFromQueue();
128 // The command will be destroyed after that, so do not access any members.
129 return result;
130 }
131
SetError(const Error * command_error,ErrorPtr * error)132 bool CommandInstance::SetError(const Error* command_error, ErrorPtr* error) {
133 error_ = command_error ? command_error->Clone() : nullptr;
134 FOR_EACH_OBSERVER(Observer, observers_, OnErrorChanged());
135 return SetStatus(State::kError, error);
136 }
137
138 namespace {
139
140 // Helper method to retrieve command parameters from the command definition
141 // object passed in as |json|.
142 // On success, returns |true| and the validated parameters and values through
143 // |parameters|. Otherwise returns |false| and additional error information in
144 // |error|.
GetCommandParameters(const base::DictionaryValue * json,ErrorPtr * error)145 std::unique_ptr<base::DictionaryValue> GetCommandParameters(
146 const base::DictionaryValue* json,
147 ErrorPtr* error) {
148 // Get the command parameters from 'parameters' property.
149 std::unique_ptr<base::DictionaryValue> params;
150 const base::Value* params_value = nullptr;
151 if (json->Get(commands::attributes::kCommand_Parameters, ¶ms_value)) {
152 // Make sure the "parameters" property is actually an object.
153 const base::DictionaryValue* params_dict = nullptr;
154 if (!params_value->GetAsDictionary(¶ms_dict)) {
155 return Error::AddToPrintf(error, FROM_HERE, errors::json::kObjectExpected,
156 "Property '%s' must be a JSON object",
157 commands::attributes::kCommand_Parameters);
158 }
159 params.reset(params_dict->DeepCopy());
160 } else {
161 // "parameters" are not specified. Assume empty param list.
162 params.reset(new base::DictionaryValue);
163 }
164 return params;
165 }
166
167 } // anonymous namespace
168
FromJson(const base::Value * value,Command::Origin origin,std::string * command_id,ErrorPtr * error)169 std::unique_ptr<CommandInstance> CommandInstance::FromJson(
170 const base::Value* value,
171 Command::Origin origin,
172 std::string* command_id,
173 ErrorPtr* error) {
174 std::unique_ptr<CommandInstance> instance;
175 std::string command_id_buffer; // used if |command_id| was nullptr.
176 if (!command_id)
177 command_id = &command_id_buffer;
178
179 // Get the command JSON object from the value.
180 const base::DictionaryValue* json = nullptr;
181 if (!value->GetAsDictionary(&json)) {
182 command_id->clear();
183 return Error::AddTo(error, FROM_HERE, errors::json::kObjectExpected,
184 "Command instance is not a JSON object");
185 }
186
187 // Get the command ID from 'id' property.
188 if (!json->GetString(commands::attributes::kCommand_Id, command_id))
189 command_id->clear();
190
191 // Get the command name from 'name' property.
192 std::string command_name;
193 if (!json->GetString(commands::attributes::kCommand_Name, &command_name)) {
194 return Error::AddTo(error, FROM_HERE, errors::commands::kPropertyMissing,
195 "Command name is missing");
196 }
197
198 auto parameters = GetCommandParameters(json, error);
199 if (!parameters) {
200 return Error::AddToPrintf(
201 error, FROM_HERE, errors::commands::kCommandFailed,
202 "Failed to validate command '%s'", command_name.c_str());
203 }
204
205 instance.reset(new CommandInstance{command_name, origin, *parameters});
206
207 if (!command_id->empty())
208 instance->SetID(*command_id);
209
210 // 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
215 return instance;
216 }
217
ToJson() const218 std::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_);
223 json->Set(commands::attributes::kCommand_Parameters, parameters_.DeepCopy());
224 json->Set(commands::attributes::kCommand_Progress, progress_.DeepCopy());
225 json->Set(commands::attributes::kCommand_Results, results_.DeepCopy());
226 json->SetString(commands::attributes::kCommand_State, EnumToString(state_));
227 if (error_) {
228 json->Set(commands::attributes::kCommand_Error,
229 ErrorInfoToJson(*error_).release());
230 }
231
232 return json;
233 }
234
AddObserver(Observer * observer)235 void CommandInstance::AddObserver(Observer* observer) {
236 observers_.AddObserver(observer);
237 }
238
RemoveObserver(Observer * observer)239 void CommandInstance::RemoveObserver(Observer* observer) {
240 observers_.RemoveObserver(observer);
241 }
242
Pause(ErrorPtr * error)243 bool CommandInstance::Pause(ErrorPtr* error) {
244 return SetStatus(State::kPaused, error);
245 }
246
Abort(const Error * command_error,ErrorPtr * error)247 bool CommandInstance::Abort(const Error* command_error, ErrorPtr* error) {
248 error_ = command_error ? command_error->Clone() : nullptr;
249 FOR_EACH_OBSERVER(Observer, observers_, OnErrorChanged());
250 bool result = SetStatus(State::kAborted, error);
251 RemoveFromQueue();
252 // The command will be destroyed after that, so do not access any members.
253 return result;
254 }
255
Cancel(ErrorPtr * error)256 bool CommandInstance::Cancel(ErrorPtr* error) {
257 bool result = SetStatus(State::kCancelled, error);
258 RemoveFromQueue();
259 // The command will be destroyed after that, so do not access any members.
260 return result;
261 }
262
SetStatus(Command::State status,ErrorPtr * error)263 bool CommandInstance::SetStatus(Command::State status, ErrorPtr* error) {
264 if (status == state_)
265 return true;
266 if (status == State::kQueued)
267 return ReportInvalidStateTransition(error, state_, status);
268 switch (state_) {
269 case State::kDone:
270 case State::kCancelled:
271 case State::kAborted:
272 case State::kExpired:
273 return ReportInvalidStateTransition(error, state_, status);
274 case State::kQueued:
275 case State::kInProgress:
276 case State::kPaused:
277 case State::kError:
278 break;
279 }
280 state_ = status;
281 FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
282 return true;
283 }
284
RemoveFromQueue()285 void CommandInstance::RemoveFromQueue() {
286 if (queue_)
287 queue_->RemoveLater(GetID());
288 }
289
290 } // namespace weave
291