Home | History | Annotate | Download | only in commands
      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(&parameters);
     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, &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 
    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