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(&parameters);
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, &params_value)) {
152     // Make sure the "parameters" property is actually an object.
153     const base::DictionaryValue* params_dict = nullptr;
154     if (!params_value->GetAsDictionary(&params_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