blob: a8891f7f0e6022ef90ffe035083c0fa9803bd0cd [file] [log] [blame]
// Copyright 2014 The Chromium OS 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"
namespace weave {
namespace {
const EnumToStringMap<CommandStatus>::Map kMapStatus[] = {
{CommandStatus::kQueued, "queued"},
{CommandStatus::kInProgress, "inProgress"},
{CommandStatus::kPaused, "paused"},
{CommandStatus::kError, "error"},
{CommandStatus::kDone, "done"},
{CommandStatus::kCancelled, "cancelled"},
{CommandStatus::kAborted, "aborted"},
{CommandStatus::kExpired, "expired"},
};
const EnumToStringMap<CommandOrigin>::Map kMapOrigin[] = {
{CommandOrigin::kLocal, "local"},
{CommandOrigin::kCloud, "cloud"},
};
} // namespace
template <>
LIBWEAVE_EXPORT EnumToStringMap<CommandStatus>::EnumToStringMap()
: EnumToStringMap(kMapStatus) {}
template <>
LIBWEAVE_EXPORT EnumToStringMap<CommandOrigin>::EnumToStringMap()
: EnumToStringMap(kMapOrigin) {}
CommandInstance::CommandInstance(const std::string& name,
CommandOrigin 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_;
}
CommandStatus CommandInstance::GetStatus() const {
return status_;
}
CommandOrigin 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_);
}
bool CommandInstance::SetProgress(const base::DictionaryValue& progress,
ErrorPtr* 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%.
SetStatus(CommandStatus::kInProgress);
if (obj != progress_) {
progress_ = obj;
FOR_EACH_OBSERVER(Observer, observers_, OnProgressChanged());
}
return true;
}
bool CommandInstance::SetResults(const base::DictionaryValue& results,
ErrorPtr* 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());
}
return true;
}
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, &params_value)) {
// Make sure the "parameters" property is actually an object.
if (!params_value->GetAsDictionary(&params)) {
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,
CommandOrigin 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, &parameters, 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(status_));
return json;
}
void CommandInstance::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void CommandInstance::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void CommandInstance::Abort() {
SetStatus(CommandStatus::kAborted);
RemoveFromQueue();
// The command will be destroyed after that, so do not access any members.
}
void CommandInstance::Cancel() {
SetStatus(CommandStatus::kCancelled);
RemoveFromQueue();
// The command will be destroyed after that, so do not access any members.
}
void CommandInstance::Done() {
SetStatus(CommandStatus::kDone);
RemoveFromQueue();
// The command will be destroyed after that, so do not access any members.
}
void CommandInstance::SetStatus(CommandStatus status) {
if (status != status_) {
status_ = status;
FOR_EACH_OBSERVER(Observer, observers_, OnStatusChanged());
}
}
void CommandInstance::RemoveFromQueue() {
if (queue_)
queue_->DelayedRemove(GetID());
}
} // namespace weave