|  | // Copyright 2015 The Weave Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "src/commands/command_instance.h" | 
|  |  | 
|  | #include <base/values.h> | 
|  | #include <weave/enum_to_string.h> | 
|  | #include <weave/error.h> | 
|  | #include <weave/export.h> | 
|  |  | 
|  | #include "src/commands/command_definition.h" | 
|  | #include "src/commands/command_dictionary.h" | 
|  | #include "src/commands/command_queue.h" | 
|  | #include "src/commands/prop_types.h" | 
|  | #include "src/commands/schema_constants.h" | 
|  | #include "src/commands/schema_utils.h" | 
|  | #include "src/json_error_codes.h" | 
|  | #include "src/utils.h" | 
|  |  | 
|  | namespace weave { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const EnumToStringMap<Command::State>::Map kMapStatus[] = { | 
|  | {Command::State::kQueued, "queued"}, | 
|  | {Command::State::kInProgress, "inProgress"}, | 
|  | {Command::State::kPaused, "paused"}, | 
|  | {Command::State::kError, "error"}, | 
|  | {Command::State::kDone, "done"}, | 
|  | {Command::State::kCancelled, "cancelled"}, | 
|  | {Command::State::kAborted, "aborted"}, | 
|  | {Command::State::kExpired, "expired"}, | 
|  | }; | 
|  |  | 
|  | const EnumToStringMap<Command::Origin>::Map kMapOrigin[] = { | 
|  | {Command::Origin::kLocal, "local"}, | 
|  | {Command::Origin::kCloud, "cloud"}, | 
|  | }; | 
|  |  | 
|  | bool ReportDestroyedError(ErrorPtr* error) { | 
|  | Error::AddTo(error, FROM_HERE, errors::commands::kDomain, | 
|  | errors::commands::kCommandDestroyed, | 
|  | "Command has been destroyed"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool ReportInvalidStateTransition(ErrorPtr* error, | 
|  | Command::State from, | 
|  | Command::State to) { | 
|  | Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, | 
|  | errors::commands::kInvalidState, | 
|  | "State switch impossible: '%s' -> '%s'", | 
|  | EnumToString(from).c_str(), EnumToString(to).c_str()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | template <> | 
|  | LIBWEAVE_EXPORT EnumToStringMap<Command::State>::EnumToStringMap() | 
|  | : EnumToStringMap(kMapStatus) {} | 
|  |  | 
|  | template <> | 
|  | LIBWEAVE_EXPORT EnumToStringMap<Command::Origin>::EnumToStringMap() | 
|  | : EnumToStringMap(kMapOrigin) {} | 
|  |  | 
|  | CommandInstance::CommandInstance(const std::string& name, | 
|  | Command::Origin origin, | 
|  | const CommandDefinition* command_definition, | 
|  | const ValueMap& parameters) | 
|  | : name_{name}, | 
|  | origin_{origin}, | 
|  | command_definition_{command_definition}, | 
|  | parameters_{parameters} { | 
|  | CHECK(command_definition_); | 
|  | } | 
|  |  | 
|  | CommandInstance::~CommandInstance() { | 
|  | FOR_EACH_OBSERVER(Observer, observers_, OnCommandDestroyed()); | 
|  | } | 
|  |  | 
|  | const std::string& CommandInstance::GetID() const { | 
|  | return id_; | 
|  | } | 
|  |  | 
|  | const std::string& CommandInstance::GetName() const { | 
|  | return name_; | 
|  | } | 
|  |  | 
|  | Command::State CommandInstance::GetState() const { | 
|  | return state_; | 
|  | } | 
|  |  | 
|  | Command::Origin CommandInstance::GetOrigin() const { | 
|  | return origin_; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> CommandInstance::GetParameters() const { | 
|  | return TypedValueToJson(parameters_); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> CommandInstance::GetProgress() const { | 
|  | return TypedValueToJson(progress_); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> CommandInstance::GetResults() const { | 
|  | return TypedValueToJson(results_); | 
|  | } | 
|  |  | 
|  | const Error* CommandInstance::GetError() const { | 
|  | return error_.get(); | 
|  | } | 
|  |  | 
|  | bool CommandInstance::SetProgress(const base::DictionaryValue& progress, | 
|  | ErrorPtr* error) { | 
|  | if (!command_definition_) | 
|  | return ReportDestroyedError(error); | 
|  | ObjectPropType obj_prop_type; | 
|  | obj_prop_type.SetObjectSchema(command_definition_->GetProgress()->Clone()); | 
|  |  | 
|  | ValueMap obj; | 
|  | if (!TypedValueFromJson(&progress, &obj_prop_type, &obj, error)) | 
|  | return false; | 
|  |  | 
|  | // Change status even if progress unchanged, e.g. 0% -> 0%. | 
|  | if (!SetStatus(State::kInProgress, error)) | 
|  | return false; | 
|  |  | 
|  | if (obj != progress_) { | 
|  | progress_ = obj; | 
|  | FOR_EACH_OBSERVER(Observer, observers_, OnProgressChanged()); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CommandInstance::Complete(const base::DictionaryValue& results, | 
|  | ErrorPtr* error) { | 
|  | if (!command_definition_) | 
|  | return ReportDestroyedError(error); | 
|  | ObjectPropType obj_prop_type; | 
|  | obj_prop_type.SetObjectSchema(command_definition_->GetResults()->Clone()); | 
|  |  | 
|  | ValueMap obj; | 
|  | if (!TypedValueFromJson(&results, &obj_prop_type, &obj, error)) | 
|  | return false; | 
|  |  | 
|  | if (obj != results_) { | 
|  | results_ = obj; | 
|  | FOR_EACH_OBSERVER(Observer, observers_, OnResultsChanged()); | 
|  | } | 
|  | // Change status even if result is unchanged. | 
|  | bool result = SetStatus(State::kDone, error); | 
|  | RemoveFromQueue(); | 
|  | // The command will be destroyed after that, so do not access any members. | 
|  | return result; | 
|  | } | 
|  |  | 
|  | bool CommandInstance::SetError(const Error* command_error, ErrorPtr* error) { | 
|  | error_ = command_error ? command_error->Clone() : nullptr; | 
|  | FOR_EACH_OBSERVER(Observer, observers_, OnErrorChanged()); | 
|  | return SetStatus(State::kError, error); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Helper method to retrieve command parameters from the command definition | 
|  | // object passed in as |json| and corresponding command definition schema | 
|  | // specified in |command_def|. | 
|  | // On success, returns |true| and the validated parameters and values through | 
|  | // |parameters|. Otherwise returns |false| and additional error information in | 
|  | // |error|. | 
|  | bool GetCommandParameters(const base::DictionaryValue* json, | 
|  | const CommandDefinition* command_def, | 
|  | ValueMap* parameters, | 
|  | ErrorPtr* error) { | 
|  | // Get the command parameters from 'parameters' property. | 
|  | base::DictionaryValue no_params;  // Placeholder when no params are specified. | 
|  | const base::DictionaryValue* params = nullptr; | 
|  | const base::Value* params_value = nullptr; | 
|  | if (json->Get(commands::attributes::kCommand_Parameters, ¶ms_value)) { | 
|  | // Make sure the "parameters" property is actually an object. | 
|  | if (!params_value->GetAsDictionary(¶ms)) { | 
|  | Error::AddToPrintf(error, FROM_HERE, errors::json::kDomain, | 
|  | errors::json::kObjectExpected, | 
|  | "Property '%s' must be a JSON object", | 
|  | commands::attributes::kCommand_Parameters); | 
|  | return false; | 
|  | } | 
|  | } else { | 
|  | // "parameters" are not specified. Assume empty param list. | 
|  | params = &no_params; | 
|  | } | 
|  |  | 
|  | // Now read in the parameters and validate their values against the command | 
|  | // definition schema. | 
|  | ObjectPropType obj_prop_type; | 
|  | obj_prop_type.SetObjectSchema(command_def->GetParameters()->Clone()); | 
|  | if (!TypedValueFromJson(params, &obj_prop_type, parameters, error)) { | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // anonymous namespace | 
|  |  | 
|  | std::unique_ptr<CommandInstance> CommandInstance::FromJson( | 
|  | const base::Value* value, | 
|  | Command::Origin origin, | 
|  | const CommandDictionary& dictionary, | 
|  | std::string* command_id, | 
|  | ErrorPtr* error) { | 
|  | std::unique_ptr<CommandInstance> instance; | 
|  | std::string command_id_buffer;  // used if |command_id| was nullptr. | 
|  | if (!command_id) | 
|  | command_id = &command_id_buffer; | 
|  |  | 
|  | // Get the command JSON object from the value. | 
|  | const base::DictionaryValue* json = nullptr; | 
|  | if (!value->GetAsDictionary(&json)) { | 
|  | Error::AddTo(error, FROM_HERE, errors::json::kDomain, | 
|  | errors::json::kObjectExpected, | 
|  | "Command instance is not a JSON object"); | 
|  | command_id->clear(); | 
|  | return instance; | 
|  | } | 
|  |  | 
|  | // Get the command ID from 'id' property. | 
|  | if (!json->GetString(commands::attributes::kCommand_Id, command_id)) | 
|  | command_id->clear(); | 
|  |  | 
|  | // Get the command name from 'name' property. | 
|  | std::string command_name; | 
|  | if (!json->GetString(commands::attributes::kCommand_Name, &command_name)) { | 
|  | Error::AddTo(error, FROM_HERE, errors::commands::kDomain, | 
|  | errors::commands::kPropertyMissing, "Command name is missing"); | 
|  | return instance; | 
|  | } | 
|  | // Make sure we know how to handle the command with this name. | 
|  | auto command_def = dictionary.FindCommand(command_name); | 
|  | if (!command_def) { | 
|  | Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, | 
|  | errors::commands::kInvalidCommandName, | 
|  | "Unknown command received: %s", command_name.c_str()); | 
|  | return instance; | 
|  | } | 
|  |  | 
|  | ValueMap parameters; | 
|  | if (!GetCommandParameters(json, command_def, ¶meters, error)) { | 
|  | Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, | 
|  | errors::commands::kCommandFailed, | 
|  | "Failed to validate command '%s'", command_name.c_str()); | 
|  | return instance; | 
|  | } | 
|  |  | 
|  | instance.reset( | 
|  | new CommandInstance{command_name, origin, command_def, parameters}); | 
|  |  | 
|  | if (!command_id->empty()) | 
|  | instance->SetID(*command_id); | 
|  |  | 
|  | return instance; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> CommandInstance::ToJson() const { | 
|  | std::unique_ptr<base::DictionaryValue> json{new base::DictionaryValue}; | 
|  |  | 
|  | json->SetString(commands::attributes::kCommand_Id, id_); | 
|  | json->SetString(commands::attributes::kCommand_Name, name_); | 
|  | json->Set(commands::attributes::kCommand_Parameters, | 
|  | TypedValueToJson(parameters_).release()); | 
|  | json->Set(commands::attributes::kCommand_Progress, | 
|  | TypedValueToJson(progress_).release()); | 
|  | json->Set(commands::attributes::kCommand_Results, | 
|  | TypedValueToJson(results_).release()); | 
|  | json->SetString(commands::attributes::kCommand_State, EnumToString(state_)); | 
|  | if (error_) { | 
|  | json->Set(commands::attributes::kCommand_Error, | 
|  | ErrorInfoToJson(*error_).release()); | 
|  | } | 
|  |  | 
|  | return json; | 
|  | } | 
|  |  | 
|  | void CommandInstance::AddObserver(Observer* observer) { | 
|  | observers_.AddObserver(observer); | 
|  | } | 
|  |  | 
|  | void CommandInstance::RemoveObserver(Observer* observer) { | 
|  | observers_.RemoveObserver(observer); | 
|  | } | 
|  |  | 
|  | bool CommandInstance::Pause(ErrorPtr* error) { | 
|  | return SetStatus(State::kPaused, error); | 
|  | } | 
|  |  | 
|  | bool CommandInstance::Abort(const Error* command_error, ErrorPtr* error) { | 
|  | error_ = command_error ? command_error->Clone() : nullptr; | 
|  | FOR_EACH_OBSERVER(Observer, observers_, OnErrorChanged()); | 
|  | bool result = SetStatus(State::kAborted, error); | 
|  | RemoveFromQueue(); | 
|  | // The command will be destroyed after that, so do not access any members. | 
|  | return result; | 
|  | } | 
|  |  | 
|  | bool CommandInstance::Cancel(ErrorPtr* error) { | 
|  | bool result = SetStatus(State::kCancelled, error); | 
|  | RemoveFromQueue(); | 
|  | // The command will be destroyed after that, so do not access any members. | 
|  | return result; | 
|  | } | 
|  |  | 
|  | bool CommandInstance::SetStatus(Command::State status, ErrorPtr* error) { | 
|  | if (status == state_) | 
|  | return true; | 
|  | if (status == State::kQueued) | 
|  | return ReportInvalidStateTransition(error, state_, status); | 
|  | switch (state_) { | 
|  | case State::kDone: | 
|  | case State::kCancelled: | 
|  | case State::kAborted: | 
|  | case State::kExpired: | 
|  | return ReportInvalidStateTransition(error, state_, status); | 
|  | case State::kQueued: | 
|  | case State::kInProgress: | 
|  | case State::kPaused: | 
|  | case State::kError: | 
|  | break; | 
|  | } | 
|  | state_ = status; | 
|  | FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void CommandInstance::RemoveFromQueue() { | 
|  | if (queue_) | 
|  | queue_->DelayedRemove(GetID()); | 
|  | } | 
|  |  | 
|  | }  // namespace weave |