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 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 <> 49 LIBWEAVE_EXPORT EnumToStringMap<Command::State>::EnumToStringMap() 50 : EnumToStringMap(kMapStatus) {} 51 52 template <> 53 LIBWEAVE_EXPORT EnumToStringMap<Command::Origin>::EnumToStringMap() 54 : EnumToStringMap(kMapOrigin) {} 55 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 63 CommandInstance::~CommandInstance() { 64 FOR_EACH_OBSERVER(Observer, observers_, OnCommandDestroyed()); 65 } 66 67 const std::string& CommandInstance::GetID() const { 68 return id_; 69 } 70 71 const std::string& CommandInstance::GetName() const { 72 return name_; 73 } 74 75 const std::string& CommandInstance::GetComponent() const { 76 return component_; 77 } 78 79 Command::State CommandInstance::GetState() const { 80 return state_; 81 } 82 83 Command::Origin CommandInstance::GetOrigin() const { 84 return origin_; 85 } 86 87 const base::DictionaryValue& CommandInstance::GetParameters() const { 88 return parameters_; 89 } 90 91 const base::DictionaryValue& CommandInstance::GetProgress() const { 92 return progress_; 93 } 94 95 const base::DictionaryValue& CommandInstance::GetResults() const { 96 return results_; 97 } 98 99 const Error* CommandInstance::GetError() const { 100 return error_.get(); 101 } 102 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 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 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|. 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 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 218 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 235 void CommandInstance::AddObserver(Observer* observer) { 236 observers_.AddObserver(observer); 237 } 238 239 void CommandInstance::RemoveObserver(Observer* observer) { 240 observers_.RemoveObserver(observer); 241 } 242 243 bool CommandInstance::Pause(ErrorPtr* error) { 244 return SetStatus(State::kPaused, error); 245 } 246 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 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 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 285 void CommandInstance::RemoveFromQueue() { 286 if (queue_) 287 queue_->RemoveLater(GetID()); 288 } 289 290 } // namespace weave 291