Alex Vakulenko | 3cc95aa | 2014-08-07 07:24:06 -0700 | [diff] [blame] | 1 | // Copyright 2014 The Chromium OS 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 "buffet/commands/command_instance.h" |
| 6 | |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 7 | #include <base/values.h> |
Alex Vakulenko | 24e5f5d | 2014-08-27 11:00:57 -0700 | [diff] [blame] | 8 | #include <chromeos/errors/error.h> |
| 9 | #include <chromeos/errors/error_codes.h> |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 10 | |
| 11 | #include "buffet/commands/command_definition.h" |
| 12 | #include "buffet/commands/command_dictionary.h" |
Alex Vakulenko | 9158b44 | 2014-09-03 16:23:38 -0700 | [diff] [blame] | 13 | #include "buffet/commands/command_proxy_interface.h" |
| 14 | #include "buffet/commands/command_queue.h" |
Alex Vakulenko | c1459e6 | 2015-03-18 09:54:37 -0700 | [diff] [blame] | 15 | #include "buffet/commands/prop_types.h" |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 16 | #include "buffet/commands/schema_constants.h" |
| 17 | #include "buffet/commands/schema_utils.h" |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 18 | |
Alex Vakulenko | 3cc95aa | 2014-08-07 07:24:06 -0700 | [diff] [blame] | 19 | namespace buffet { |
| 20 | |
Alex Vakulenko | 9158b44 | 2014-09-03 16:23:38 -0700 | [diff] [blame] | 21 | const char CommandInstance::kStatusQueued[] = "queued"; |
| 22 | const char CommandInstance::kStatusInProgress[] = "inProgress"; |
| 23 | const char CommandInstance::kStatusPaused[] = "paused"; |
| 24 | const char CommandInstance::kStatusError[] = "error"; |
| 25 | const char CommandInstance::kStatusDone[] = "done"; |
Alex Vakulenko | 71beab3 | 2015-03-13 14:02:46 -0700 | [diff] [blame] | 26 | const char CommandInstance::kStatusCancelled[] = "cancelled"; |
Alex Vakulenko | 9158b44 | 2014-09-03 16:23:38 -0700 | [diff] [blame] | 27 | const char CommandInstance::kStatusAborted[] = "aborted"; |
| 28 | const char CommandInstance::kStatusExpired[] = "expired"; |
| 29 | |
Alex Vakulenko | 599ec76 | 2015-03-19 15:50:44 -0700 | [diff] [blame] | 30 | CommandInstance::CommandInstance(const std::string& name, |
| 31 | const CommandDefinition* command_definition, |
| 32 | const native_types::Object& parameters) |
| 33 | : name_{name}, |
| 34 | command_definition_{command_definition}, |
| 35 | parameters_{parameters} { |
| 36 | CHECK(command_definition_); |
Alex Vakulenko | 3cc95aa | 2014-08-07 07:24:06 -0700 | [diff] [blame] | 37 | } |
| 38 | |
Anton Muhin | 182fad3 | 2014-11-10 22:15:22 +0400 | [diff] [blame] | 39 | CommandInstance::~CommandInstance() = default; |
| 40 | |
Anton Muhin | 9ac4586 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 41 | const std::string& CommandInstance::GetCategory() const { |
| 42 | return command_definition_->GetCategory(); |
| 43 | } |
| 44 | |
Alex Vakulenko | 599ec76 | 2015-03-19 15:50:44 -0700 | [diff] [blame] | 45 | const PropValue* CommandInstance::FindParameter(const std::string& name) const { |
Alex Vakulenko | 3cc95aa | 2014-08-07 07:24:06 -0700 | [diff] [blame] | 46 | auto p = parameters_.find(name); |
Alex Vakulenko | 599ec76 | 2015-03-19 15:50:44 -0700 | [diff] [blame] | 47 | return (p != parameters_.end()) ? p->second.get() : nullptr; |
Alex Vakulenko | 3cc95aa | 2014-08-07 07:24:06 -0700 | [diff] [blame] | 48 | } |
| 49 | |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 50 | namespace { |
| 51 | |
| 52 | // Helper method to retrieve command parameters from the command definition |
| 53 | // object passed in as |json| and corresponding command definition schema |
| 54 | // specified in |command_def|. |
| 55 | // On success, returns |true| and the validated parameters and values through |
| 56 | // |parameters|. Otherwise returns |false| and additional error information in |
| 57 | // |error|. |
| 58 | bool GetCommandParameters(const base::DictionaryValue* json, |
| 59 | const CommandDefinition* command_def, |
| 60 | native_types::Object* parameters, |
Alex Vakulenko | fea44e9 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 61 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 62 | // Get the command parameters from 'parameters' property. |
| 63 | base::DictionaryValue no_params; // Placeholder when no params are specified. |
| 64 | const base::DictionaryValue* params = nullptr; |
| 65 | const base::Value* params_value = nullptr; |
| 66 | if (json->GetWithoutPathExpansion(commands::attributes::kCommand_Parameters, |
| 67 | ¶ms_value)) { |
| 68 | // Make sure the "parameters" property is actually an object. |
| 69 | if (!params_value->GetAsDictionary(¶ms)) { |
Alex Vakulenko | 90b3da9 | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 70 | chromeos::Error::AddToPrintf(error, FROM_HERE, |
| 71 | chromeos::errors::json::kDomain, |
Alex Vakulenko | fea44e9 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 72 | chromeos::errors::json::kObjectExpected, |
| 73 | "Property '%s' must be a JSON object", |
| 74 | commands::attributes::kCommand_Parameters); |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 75 | return false; |
| 76 | } |
| 77 | } else { |
| 78 | // "parameters" are not specified. Assume empty param list. |
| 79 | params = &no_params; |
| 80 | } |
| 81 | |
| 82 | // Now read in the parameters and validate their values against the command |
| 83 | // definition schema. |
Alex Vakulenko | c1459e6 | 2015-03-18 09:54:37 -0700 | [diff] [blame] | 84 | ObjectPropType obj_prop_type; |
Alex Vakulenko | 599ec76 | 2015-03-19 15:50:44 -0700 | [diff] [blame] | 85 | obj_prop_type.SetObjectSchema(command_def->GetParameters()->Clone()); |
Alex Vakulenko | c1459e6 | 2015-03-18 09:54:37 -0700 | [diff] [blame] | 86 | if (!TypedValueFromJson(params, &obj_prop_type, parameters, error)) { |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 87 | return false; |
| 88 | } |
| 89 | return true; |
| 90 | } |
| 91 | |
| 92 | } // anonymous namespace |
| 93 | |
Alex Vakulenko | 6c5a602 | 2014-08-20 12:38:43 -0700 | [diff] [blame] | 94 | std::unique_ptr<CommandInstance> CommandInstance::FromJson( |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 95 | const base::Value* value, |
| 96 | const CommandDictionary& dictionary, |
Alex Vakulenko | fea44e9 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 97 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 6c5a602 | 2014-08-20 12:38:43 -0700 | [diff] [blame] | 98 | std::unique_ptr<CommandInstance> instance; |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 99 | // Get the command JSON object from the value. |
| 100 | const base::DictionaryValue* json = nullptr; |
| 101 | if (!value->GetAsDictionary(&json)) { |
Alex Vakulenko | 90b3da9 | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 102 | chromeos::Error::AddTo(error, FROM_HERE, chromeos::errors::json::kDomain, |
Alex Vakulenko | fea44e9 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 103 | chromeos::errors::json::kObjectExpected, |
| 104 | "Command instance is not a JSON object"); |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 105 | return instance; |
| 106 | } |
| 107 | |
| 108 | // Get the command name from 'name' property. |
| 109 | std::string command_name; |
| 110 | if (!json->GetStringWithoutPathExpansion(commands::attributes::kCommand_Name, |
| 111 | &command_name)) { |
Alex Vakulenko | 90b3da9 | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 112 | chromeos::Error::AddTo(error, FROM_HERE, errors::commands::kDomain, |
Alex Vakulenko | fea44e9 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 113 | errors::commands::kPropertyMissing, |
| 114 | "Command name is missing"); |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 115 | return instance; |
| 116 | } |
| 117 | // Make sure we know how to handle the command with this name. |
Anton Muhin | 9ac4586 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 118 | auto command_def = dictionary.FindCommand(command_name); |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 119 | if (!command_def) { |
Alex Vakulenko | 90b3da9 | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 120 | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
Alex Vakulenko | fea44e9 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 121 | errors::commands::kInvalidCommandName, |
| 122 | "Unknown command received: %s", |
| 123 | command_name.c_str()); |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 124 | return instance; |
| 125 | } |
| 126 | |
| 127 | native_types::Object parameters; |
Alex Vakulenko | 599ec76 | 2015-03-19 15:50:44 -0700 | [diff] [blame] | 128 | if (!GetCommandParameters(json, command_def, ¶meters, error)) { |
Alex Vakulenko | 90b3da9 | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 129 | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
Alex Vakulenko | fea44e9 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 130 | errors::commands::kCommandFailed, |
| 131 | "Failed to validate command '%s'", |
| 132 | command_name.c_str()); |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 133 | return instance; |
| 134 | } |
| 135 | |
Anton Muhin | 9ac4586 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 136 | instance.reset(new CommandInstance(command_name, command_def, parameters)); |
Anton Muhin | d014d8b | 2014-10-30 17:49:48 +0400 | [diff] [blame] | 137 | std::string command_id; |
| 138 | if (json->GetStringWithoutPathExpansion(commands::attributes::kCommand_Id, |
| 139 | &command_id)) { |
| 140 | instance->SetID(command_id); |
| 141 | } |
| 142 | |
Alex Vakulenko | c2abd66 | 2014-08-07 10:29:42 -0700 | [diff] [blame] | 143 | return instance; |
| 144 | } |
| 145 | |
Vitaly Buka | 02afd03 | 2015-03-24 10:08:26 -0700 | [diff] [blame^] | 146 | std::unique_ptr<base::DictionaryValue> CommandInstance::ToJson() const { |
| 147 | std::unique_ptr<base::DictionaryValue> json{new base::DictionaryValue}; |
| 148 | |
| 149 | json->SetString(commands::attributes::kCommand_Id, id_); |
| 150 | json->SetString(commands::attributes::kCommand_Name, name_); |
| 151 | json->Set(commands::attributes::kCommand_Parameters, |
| 152 | TypedValueToJson(parameters_, nullptr).release()); |
| 153 | json->Set(commands::attributes::kCommand_Results, |
| 154 | TypedValueToJson(results_, nullptr).release()); |
| 155 | json->SetInteger(commands::attributes::kCommand_Progress, progress_); |
| 156 | json->SetString(commands::attributes::kCommand_State, status_); |
| 157 | |
| 158 | return json; |
| 159 | } |
| 160 | |
Anton Muhin | 182fad3 | 2014-11-10 22:15:22 +0400 | [diff] [blame] | 161 | void CommandInstance::AddProxy(std::unique_ptr<CommandProxyInterface> proxy) { |
| 162 | proxies_.push_back(std::move(proxy)); |
| 163 | } |
| 164 | |
Anton Muhin | 9ac4586 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 165 | bool CommandInstance::SetResults(const native_types::Object& results) { |
| 166 | // TODO(antonm): Add validation. |
| 167 | if (results != results_) { |
| 168 | results_ = results; |
| 169 | for (auto& proxy : proxies_) { |
| 170 | proxy->OnResultsChanged(results_); |
| 171 | } |
| 172 | } |
| 173 | return true; |
| 174 | } |
| 175 | |
Alex Vakulenko | 9158b44 | 2014-09-03 16:23:38 -0700 | [diff] [blame] | 176 | bool CommandInstance::SetProgress(int progress) { |
| 177 | if (progress < 0 || progress > 100) |
| 178 | return false; |
| 179 | if (progress != progress_) { |
| 180 | progress_ = progress; |
| 181 | SetStatus(kStatusInProgress); |
Anton Muhin | 5beea3c | 2014-10-30 22:17:25 +0400 | [diff] [blame] | 182 | for (auto& proxy : proxies_) { |
| 183 | proxy->OnProgressChanged(progress_); |
| 184 | } |
Alex Vakulenko | 9158b44 | 2014-09-03 16:23:38 -0700 | [diff] [blame] | 185 | } |
| 186 | return true; |
| 187 | } |
| 188 | |
| 189 | void CommandInstance::Abort() { |
| 190 | SetStatus(kStatusAborted); |
| 191 | RemoveFromQueue(); |
| 192 | // The command will be destroyed after that, so do not access any members. |
| 193 | } |
| 194 | |
| 195 | void CommandInstance::Cancel() { |
Alex Vakulenko | 71beab3 | 2015-03-13 14:02:46 -0700 | [diff] [blame] | 196 | SetStatus(kStatusCancelled); |
Alex Vakulenko | 9158b44 | 2014-09-03 16:23:38 -0700 | [diff] [blame] | 197 | RemoveFromQueue(); |
| 198 | // The command will be destroyed after that, so do not access any members. |
| 199 | } |
| 200 | |
| 201 | void CommandInstance::Done() { |
| 202 | SetProgress(100); |
| 203 | SetStatus(kStatusDone); |
| 204 | RemoveFromQueue(); |
| 205 | // The command will be destroyed after that, so do not access any members. |
| 206 | } |
| 207 | |
| 208 | void CommandInstance::SetStatus(const std::string& status) { |
| 209 | if (status != status_) { |
| 210 | status_ = status; |
Anton Muhin | 5beea3c | 2014-10-30 22:17:25 +0400 | [diff] [blame] | 211 | for (auto& proxy : proxies_) { |
| 212 | proxy->OnStatusChanged(status_); |
| 213 | } |
Alex Vakulenko | 9158b44 | 2014-09-03 16:23:38 -0700 | [diff] [blame] | 214 | } |
| 215 | } |
| 216 | |
| 217 | void CommandInstance::RemoveFromQueue() { |
| 218 | if (queue_) { |
| 219 | // Store this instance in unique_ptr until the end of this method, |
| 220 | // otherwise it will be destroyed as soon as CommandQueue::Remove() returns. |
| 221 | std::unique_ptr<CommandInstance> this_instance = queue_->Remove(GetID()); |
| 222 | // The command instance will survive till the end of this scope. |
| 223 | } |
| 224 | } |
| 225 | |
Alex Vakulenko | 3cc95aa | 2014-08-07 07:24:06 -0700 | [diff] [blame] | 226 | |
| 227 | } // namespace buffet |