Switch to use ComponentManager for traits/components Removed the old StateManager, CommandManager and related classes and switched over to using ComponentManager for all device trait and component definitions as well as device state. Change-Id: I99b99a935ba217703d31aa523a3124cca0fa3e90 Reviewed-on: https://weave-review.googlesource.com/1788 Reviewed-by: Alex Vakulenko <avakulenko@google.com>
diff --git a/libweave.gypi b/libweave.gypi index dc2e6f0..fb7dd03 100644 --- a/libweave.gypi +++ b/libweave.gypi
@@ -7,9 +7,7 @@ 'src/backoff_entry.cc', 'src/base_api_handler.cc', 'src/commands/cloud_command_proxy.cc', - 'src/commands/command_dictionary.cc', 'src/commands/command_instance.cc', - 'src/commands/command_manager.cc', 'src/commands/command_queue.cc', 'src/commands/schema_constants.cc', 'src/component_manager_impl.cc', @@ -40,10 +38,7 @@ 'src/privet/wifi_bootstrap_manager.cc', 'src/privet/wifi_ssid_generator.cc', 'src/registration_status.cc', - 'src/states/error_codes.cc', 'src/states/state_change_queue.cc', - 'src/states/state_manager.cc', - 'src/states/state_package.cc', 'src/streams.cc', 'src/string_utils.cc', 'src/utils.cc', @@ -61,9 +56,7 @@ 'src/backoff_entry_unittest.cc', 'src/base_api_handler_unittest.cc', 'src/commands/cloud_command_proxy_unittest.cc', - 'src/commands/command_dictionary_unittest.cc', 'src/commands/command_instance_unittest.cc', - 'src/commands/command_manager_unittest.cc', 'src/commands/command_queue_unittest.cc', 'src/component_manager_unittest.cc', 'src/config_unittest.cc', @@ -80,8 +73,6 @@ 'src/privet/security_manager_unittest.cc', 'src/privet/wifi_ssid_generator_unittest.cc', 'src/states/state_change_queue_unittest.cc', - 'src/states/state_manager_unittest.cc', - 'src/states/state_package_unittest.cc', 'src/streams_unittest.cc', 'src/string_utils_unittest.cc', 'src/test/weave_testrunner.cc',
diff --git a/src/base_api_handler.cc b/src/base_api_handler.cc index 1423dd1..6808949 100644 --- a/src/base_api_handler.cc +++ b/src/base_api_handler.cc
@@ -13,6 +13,8 @@ namespace weave { namespace { +const char kBaseComponent[] = "weave"; +const char kBaseTrait[] = "base"; const char kBaseStateFirmwareVersion[] = "base.firmwareVersion"; const char kBaseStateAnonymousAccessRole[] = "base.localAnonymousAccessMaxRole"; const char kBaseStateDiscoveryEnabled[] = "base.localDiscoveryEnabled"; @@ -22,61 +24,64 @@ BaseApiHandler::BaseApiHandler(DeviceRegistrationInfo* device_info, Device* device) : device_info_{device_info}, device_{device} { - device_->AddStateDefinitionsFromJson(R"({ + device_->AddTraitDefinitionsFromJson(R"({ "base": { - "firmwareVersion": "string", - "localDiscoveryEnabled": "boolean", - "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ], - "localPairingEnabled": "boolean" + "commands": { + "updateBaseConfiguration": { + "minimalRole": "manager", + "parameters": { + "localAnonymousAccessMaxRole": { + "enum": [ "none", "viewer", "user" ], + "type": "string" + }, + "localDiscoveryEnabled": { + "type": "boolean" + }, + "localPairingEnabled": { + "type": "boolean" + } + } + }, + "updateDeviceInfo": { + "minimalRole": "manager", + "parameters": { + "description": { + "type": "string" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "state": { + "firmwareVersion": "string", + "localDiscoveryEnabled": "boolean", + "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ], + "localPairingEnabled": "boolean" + } } })"); + CHECK(device_->AddComponent(kBaseComponent, {kBaseTrait}, nullptr)); OnConfigChanged(device_->GetSettings()); const auto& settings = device_info_->GetSettings(); base::DictionaryValue state; state.SetString(kBaseStateFirmwareVersion, settings.firmware_version); - CHECK(device_->SetStateProperties(state, nullptr)); - - device->AddCommandDefinitionsFromJson(R"({ - "base": { - "updateBaseConfiguration": { - "minimalRole": "manager", - "parameters": { - "localAnonymousAccessMaxRole": { - "enum": [ "none", "viewer", "user" ], - "type": "string" - }, - "localDiscoveryEnabled": { - "type": "boolean" - }, - "localPairingEnabled": { - "type": "boolean" - } - } - }, - "updateDeviceInfo": { - "minimalRole": "manager", - "parameters": { - "description": { - "type": "string" - }, - "location": { - "type": "string" - }, - "name": { - "type": "string" - } - } - } - } - })"); + CHECK(device_->SetStateProperty(kBaseComponent, kBaseStateFirmwareVersion, + base::StringValue{settings.firmware_version}, + nullptr)); device_->AddCommandHandler( + kBaseComponent, "base.updateBaseConfiguration", base::Bind(&BaseApiHandler::UpdateBaseConfiguration, weak_ptr_factory_.GetWeakPtr())); - device_->AddCommandHandler("base.updateDeviceInfo", + device_->AddCommandHandler(kBaseComponent, "base.updateDeviceInfo", base::Bind(&BaseApiHandler::UpdateDeviceInfo, weak_ptr_factory_.GetWeakPtr())); @@ -128,7 +133,7 @@ state.SetBoolean(kBaseStateDiscoveryEnabled, settings.local_discovery_enabled); state.SetBoolean(kBaseStatePairingEnabled, settings.local_pairing_enabled); - device_->SetStateProperties(state, nullptr); + device_->SetStateProperties(kBaseComponent, state, nullptr); } void BaseApiHandler::UpdateDeviceInfo(const std::weak_ptr<Command>& cmd) {
diff --git a/src/base_api_handler_unittest.cc b/src/base_api_handler_unittest.cc index 5c6a8a5..23ef95e 100644 --- a/src/base_api_handler_unittest.cc +++ b/src/base_api_handler_unittest.cc
@@ -12,11 +12,9 @@ #include <weave/test/mock_device.h> #include <weave/test/unittest_utils.h> -#include "src/commands/command_manager.h" +#include "src/component_manager_impl.h" #include "src/config.h" #include "src/device_registration_info.h" -#include "src/states/mock_state_change_queue_interface.h" -#include "src/states/state_manager.h" using testing::_; using testing::AnyOf; @@ -31,35 +29,33 @@ class BaseApiHandlerTest : public ::testing::Test { protected: void SetUp() override { - EXPECT_CALL(mock_state_change_queue_, NotifyPropertiesUpdated(_, _)) - .WillRepeatedly(Return(true)); - - command_manager_ = std::make_shared<CommandManager>(); - - state_manager_ = std::make_shared<StateManager>(&mock_state_change_queue_); - - EXPECT_CALL(device_, AddStateDefinitionsFromJson(_)) + EXPECT_CALL(device_, AddTraitDefinitionsFromJson(_)) .WillRepeatedly(Invoke([this](const std::string& json) { - EXPECT_TRUE( - state_manager_->LoadStateDefinitionFromJson(json, nullptr)); + EXPECT_TRUE(component_manager_.LoadTraits(json, nullptr)); })); - EXPECT_CALL(device_, SetStateProperties(_, _)) - .WillRepeatedly( - Invoke(state_manager_.get(), &StateManager::SetProperties)); - EXPECT_CALL(device_, AddCommandDefinitionsFromJson(_)) - .WillRepeatedly(Invoke([this](const std::string& json) { - EXPECT_TRUE(command_manager_->LoadCommands(json, nullptr)); + EXPECT_CALL(device_, SetStateProperties(_, _, _)) + .WillRepeatedly(Invoke(&component_manager_, + &ComponentManager::SetStateProperties)); + EXPECT_CALL(device_, SetStateProperty(_, _, _, _)) + .WillRepeatedly(Invoke(&component_manager_, + &ComponentManager::SetStateProperty)); + EXPECT_CALL(device_, AddComponent(_, _, _)) + .WillRepeatedly(Invoke([this](const std::string& name, + const std::vector<std::string>& traits, + ErrorPtr* error) { + return component_manager_.AddComponent("", name, traits, error); })); - EXPECT_CALL(device_, AddCommandHandler(AnyOf("base.updateBaseConfiguration", + EXPECT_CALL(device_, AddCommandHandler(_, + AnyOf("base.updateBaseConfiguration", "base.updateDeviceInfo"), _)) - .WillRepeatedly( - Invoke(command_manager_.get(), &CommandManager::AddCommandHandler)); + .WillRepeatedly(Invoke(&component_manager_, + &ComponentManager::AddCommandHandler)); std::unique_ptr<Config> config{new Config{&config_store_}}; config->Load(); - dev_reg_.reset(new DeviceRegistrationInfo(command_manager_, state_manager_, + dev_reg_.reset(new DeviceRegistrationInfo(&component_manager_, std::move(config), nullptr, &http_client_, nullptr)); @@ -70,48 +66,44 @@ } void AddCommand(const std::string& command) { - auto command_instance = CommandInstance::FromJson( - test::CreateDictionaryValue(command.c_str()).get(), - Command::Origin::kLocal, nullptr, nullptr); - EXPECT_TRUE(!!command_instance); - - std::string id{base::IntToString(++command_id_)}; - command_instance->SetID(id); - command_instance->SetComponent("device"); - command_manager_->AddCommand(std::move(command_instance)); + std::string id; + auto command_instance = component_manager_.ParseCommandInstance( + *test::CreateDictionaryValue(command.c_str()), Command::Origin::kLocal, + UserRole::kOwner, &id, nullptr); + ASSERT_NE(nullptr, command_instance.get()); + component_manager_.AddCommand(std::move(command_instance)); EXPECT_EQ(Command::State::kDone, - command_manager_->FindCommand(id)->GetState()); + component_manager_.FindCommand(id)->GetState()); } std::unique_ptr<base::DictionaryValue> GetBaseState() { - std::unique_ptr<base::DictionaryValue> state{ - state_manager_->GetState().DeepCopy()}; - std::set<std::string> result; - for (base::DictionaryValue::Iterator it{*state}; !it.IsAtEnd(); - it.Advance()) { - if (it.key() != "base") - state->Remove(it.key(), nullptr); - } + std::unique_ptr<base::DictionaryValue> state; + std::string path = component_manager_.FindComponentWithTrait("base"); + EXPECT_FALSE(path.empty()); + const auto* component = component_manager_.FindComponent(path, nullptr); + CHECK(component); + const base::DictionaryValue* base_state = nullptr; + if (component->GetDictionary("state.base", &base_state)) + state.reset(base_state->DeepCopy()); + else + state.reset(new base::DictionaryValue); return state; } provider::test::MockConfigStore config_store_; StrictMock<provider::test::MockHttpClient> http_client_; std::unique_ptr<DeviceRegistrationInfo> dev_reg_; - std::shared_ptr<CommandManager> command_manager_; - testing::StrictMock<MockStateChangeQueueInterface> mock_state_change_queue_; - std::shared_ptr<StateManager> state_manager_; + ComponentManagerImpl component_manager_; std::unique_ptr<BaseApiHandler> handler_; StrictMock<test::MockDevice> device_; - int command_id_{0}; }; TEST_F(BaseApiHandlerTest, Initialization) { - const auto& command_defs = - command_manager_->GetCommandDictionary().GetCommandsAsJson(); + const base::DictionaryValue* trait = nullptr; + ASSERT_TRUE(component_manager_.GetTraits().GetDictionary("base", &trait)); auto expected = R"({ - "base": { + "commands": { "updateBaseConfiguration": { "minimalRole": "manager", "parameters": { @@ -141,9 +133,15 @@ } } } - } + }, + "state": { + "firmwareVersion": "string", + "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ], + "localDiscoveryEnabled": "boolean", + "localPairingEnabled": "boolean" + } })"; - EXPECT_JSON_EQ(expected, command_defs); + EXPECT_JSON_EQ(expected, *trait); } TEST_F(BaseApiHandlerTest, UpdateBaseConfiguration) { @@ -151,6 +149,7 @@ AddCommand(R"({ 'name' : 'base.updateBaseConfiguration', + 'component': 'weave', 'parameters': { 'localDiscoveryEnabled': false, 'localAnonymousAccessMaxRole': 'none', @@ -162,17 +161,16 @@ EXPECT_FALSE(settings.local_pairing_enabled); auto expected = R"({ - 'base': { - 'firmwareVersion': 'TEST_FIRMWARE', - 'localAnonymousAccessMaxRole': 'none', - 'localDiscoveryEnabled': false, - 'localPairingEnabled': false - } + 'firmwareVersion': 'TEST_FIRMWARE', + 'localAnonymousAccessMaxRole': 'none', + 'localDiscoveryEnabled': false, + 'localPairingEnabled': false })"; EXPECT_JSON_EQ(expected, *GetBaseState()); AddCommand(R"({ 'name' : 'base.updateBaseConfiguration', + 'component': 'weave', 'parameters': { 'localDiscoveryEnabled': true, 'localAnonymousAccessMaxRole': 'user', @@ -183,12 +181,10 @@ EXPECT_TRUE(settings.local_discovery_enabled); EXPECT_TRUE(settings.local_pairing_enabled); expected = R"({ - 'base': { - 'firmwareVersion': 'TEST_FIRMWARE', - 'localAnonymousAccessMaxRole': 'user', - 'localDiscoveryEnabled': true, - 'localPairingEnabled': true - } + 'firmwareVersion': 'TEST_FIRMWARE', + 'localAnonymousAccessMaxRole': 'user', + 'localDiscoveryEnabled': true, + 'localPairingEnabled': true })"; EXPECT_JSON_EQ(expected, *GetBaseState()); @@ -197,12 +193,10 @@ change.set_local_anonymous_access_role(AuthScope::kViewer); } expected = R"({ - 'base': { - 'firmwareVersion': 'TEST_FIRMWARE', - 'localAnonymousAccessMaxRole': 'viewer', - 'localDiscoveryEnabled': true, - 'localPairingEnabled': true - } + 'firmwareVersion': 'TEST_FIRMWARE', + 'localAnonymousAccessMaxRole': 'viewer', + 'localDiscoveryEnabled': true, + 'localPairingEnabled': true })"; EXPECT_JSON_EQ(expected, *GetBaseState()); } @@ -210,6 +204,7 @@ TEST_F(BaseApiHandlerTest, UpdateDeviceInfo) { AddCommand(R"({ 'name' : 'base.updateDeviceInfo', + 'component': 'weave', 'parameters': { 'name': 'testName', 'description': 'testDescription', @@ -224,6 +219,7 @@ AddCommand(R"({ 'name' : 'base.updateDeviceInfo', + 'component': 'weave', 'parameters': { 'location': 'newLocation' }
diff --git a/src/commands/cloud_command_proxy.cc b/src/commands/cloud_command_proxy.cc index 3d472c7..f8f8d1f 100644 --- a/src/commands/cloud_command_proxy.cc +++ b/src/commands/cloud_command_proxy.cc
@@ -17,15 +17,15 @@ CloudCommandProxy::CloudCommandProxy( CommandInstance* command_instance, CloudCommandUpdateInterface* cloud_command_updater, - StateChangeQueueInterface* state_change_queue, + ComponentManager* component_manager, std::unique_ptr<BackoffEntry> backoff_entry, provider::TaskRunner* task_runner) : command_instance_{command_instance}, cloud_command_updater_{cloud_command_updater}, - state_change_queue_{state_change_queue}, + component_manager_{component_manager}, task_runner_{task_runner}, cloud_backoff_entry_{std::move(backoff_entry)} { - callback_token_ = state_change_queue_->AddOnStateUpdatedCallback( + callback_token_ = component_manager_->AddServerStateUpdatedCallback( base::Bind(&CloudCommandProxy::OnDeviceStateUpdated, weak_ptr_factory_.GetWeakPtr())); observer_.Add(command_instance); @@ -67,7 +67,7 @@ void CloudCommandProxy::QueueCommandUpdate( std::unique_ptr<base::DictionaryValue> patch) { - UpdateID id = state_change_queue_->GetLastStateChangeId(); + ComponentManager::UpdateID id = component_manager_->GetLastStateChangeId(); if (update_queue_.empty() || update_queue_.back().first != id) { // If queue is currently empty or the device state has changed since the // last patch request queued, add a new request to the queue. @@ -158,7 +158,8 @@ SendCommandUpdate(); } -void CloudCommandProxy::OnDeviceStateUpdated(UpdateID update_id) { +void CloudCommandProxy::OnDeviceStateUpdated( + ComponentManager::UpdateID update_id) { last_state_update_id_ = update_id; // Try to send out any queued command updates that could be performed after // a device state is updated.
diff --git a/src/commands/cloud_command_proxy.h b/src/commands/cloud_command_proxy.h index ee6358f..13f4654 100644 --- a/src/commands/cloud_command_proxy.h +++ b/src/commands/cloud_command_proxy.h
@@ -18,7 +18,7 @@ #include "src/backoff_entry.h" #include "src/commands/cloud_command_update_interface.h" #include "src/commands/command_instance.h" -#include "src/states/state_change_queue_interface.h" +#include "src/component_manager.h" namespace weave { @@ -33,7 +33,7 @@ public: CloudCommandProxy(CommandInstance* command_instance, CloudCommandUpdateInterface* cloud_command_updater, - StateChangeQueueInterface* state_change_queue, + ComponentManager* component_manager, std::unique_ptr<BackoffEntry> backoff_entry, provider::TaskRunner* task_runner); ~CloudCommandProxy() override = default; @@ -46,10 +46,8 @@ void OnStateChanged() override; private: - using UpdateID = StateChangeQueueInterface::UpdateID; - using UpdateQueueEntry = - std::pair<UpdateID, std::unique_ptr<base::DictionaryValue>>; - + using UpdateQueueEntry = std::pair<ComponentManager::UpdateID, + std::unique_ptr<base::DictionaryValue>>; // Puts a command update data into the update queue, and optionally sends an // asynchronous request to GCD server to update the command resource, if there // are no pending device status updates. @@ -68,11 +66,11 @@ // Callback invoked by the device state change queue to notify of the // successful device state update. |update_id| is the ID of the state that // has been updated on the server. - void OnDeviceStateUpdated(UpdateID update_id); + void OnDeviceStateUpdated(ComponentManager::UpdateID update_id); CommandInstance* command_instance_; CloudCommandUpdateInterface* cloud_command_updater_; - StateChangeQueueInterface* state_change_queue_; + ComponentManager* component_manager_; provider::TaskRunner* task_runner_{nullptr}; // Backoff for SendCommandUpdate() method. @@ -87,11 +85,11 @@ // Callback token from the state change queue for OnDeviceStateUpdated() // callback for ask the device state change queue to call when the state // is updated on the server. - StateChangeQueueInterface::Token callback_token_; + ComponentManager::Token callback_token_; // Last device state update ID that has been sent out to the server // successfully. - UpdateID last_state_update_id_{0}; + ComponentManager::UpdateID last_state_update_id_{0}; ScopedObserver<CommandInstance, CommandInstance::Observer> observer_{this};
diff --git a/src/commands/cloud_command_proxy_unittest.cc b/src/commands/cloud_command_proxy_unittest.cc index c022b79..d3a9965 100644 --- a/src/commands/cloud_command_proxy_unittest.cc +++ b/src/commands/cloud_command_proxy_unittest.cc
@@ -12,9 +12,8 @@ #include <weave/provider/test/fake_task_runner.h> #include <weave/test/unittest_utils.h> -#include "src/commands/command_dictionary.h" #include "src/commands/command_instance.h" -#include "src/states/mock_state_change_queue_interface.h" +#include "src/mock_component_manager.h" using testing::_; using testing::DoAll; @@ -66,14 +65,14 @@ class CloudCommandProxyTest : public ::testing::Test { protected: void SetUp() override { - // Set up the test StateChangeQueue. - auto callback = [this]( - const base::Callback<void(StateChangeQueueInterface::UpdateID)>& call) { + // Set up the test ComponentManager. + auto callback = + [this](const base::Callback<void(ComponentManager::UpdateID)>& call) { return callbacks_.Add(call).release(); }; - EXPECT_CALL(state_change_queue_, MockAddOnStateUpdatedCallback(_)) + EXPECT_CALL(component_manager_, MockAddServerStateUpdatedCallback(_)) .WillRepeatedly(Invoke(callback)); - EXPECT_CALL(state_change_queue_, GetLastStateChangeId()) + EXPECT_CALL(component_manager_, GetLastStateChangeId()) .WillRepeatedly(testing::ReturnPointee(¤t_state_update_id_)); CreateCommandInstance(); @@ -102,7 +101,7 @@ // Finally construct the CloudCommandProxy we are going to test here. std::unique_ptr<CloudCommandProxy> proxy{new CloudCommandProxy{ - command_instance_.get(), &cloud_updater_, &state_change_queue_, + command_instance_.get(), &cloud_updater_, &component_manager_, std::move(backoff), &task_runner_}}; // CloudCommandProxy::CloudCommandProxy() subscribe itself to weave::Command // notifications. When weave::Command is being destroyed it sends @@ -110,10 +109,10 @@ proxy.release(); } - StateChangeQueueInterface::UpdateID current_state_update_id_{0}; - base::CallbackList<void(StateChangeQueueInterface::UpdateID)> callbacks_; + ComponentManager::UpdateID current_state_update_id_{0}; + base::CallbackList<void(ComponentManager::UpdateID)> callbacks_; testing::StrictMock<MockCloudCommandUpdateInterface> cloud_updater_; - testing::StrictMock<MockStateChangeQueueInterface> state_change_queue_; + testing::StrictMock<MockComponentManager> component_manager_; testing::StrictMock<provider::test::FakeTaskRunner> task_runner_; std::queue<base::Closure> task_queue_; std::unique_ptr<CommandInstance> command_instance_;
diff --git a/src/commands/command_dictionary.cc b/src/commands/command_dictionary.cc deleted file mode 100644 index f6a409d..0000000 --- a/src/commands/command_dictionary.cc +++ /dev/null
@@ -1,151 +0,0 @@ -// 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_dictionary.h" - -#include <algorithm> - -#include <base/values.h> -#include <weave/enum_to_string.h> - -#include "src/commands/schema_constants.h" -#include "src/string_utils.h" - -namespace weave { - -namespace { -const EnumToStringMap<UserRole>::Map kMap[] = { - {UserRole::kViewer, commands::attributes::kCommand_Role_Viewer}, - {UserRole::kUser, commands::attributes::kCommand_Role_User}, - {UserRole::kOwner, commands::attributes::kCommand_Role_Owner}, - {UserRole::kManager, commands::attributes::kCommand_Role_Manager}, -}; -} // anonymous namespace - -template <> -LIBWEAVE_EXPORT EnumToStringMap<UserRole>::EnumToStringMap() - : EnumToStringMap(kMap) {} - -bool CommandDictionary::LoadCommands(const base::DictionaryValue& json, - ErrorPtr* error) { - // |json| contains a list of nested objects with the following structure: - // {"<pkg_name>": {"<cmd_name>": {"parameters": {object_schema}}, ...}, ...} - // Iterate over traits - base::DictionaryValue::Iterator trait_iter(json); - for (base::DictionaryValue::Iterator trait_iter(json); - !trait_iter.IsAtEnd(); trait_iter.Advance()) { - std::string trait_name = trait_iter.key(); - const base::DictionaryValue* trait_def = nullptr; - if (!trait_iter.value().GetAsDictionary(&trait_def)) { - Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, - errors::commands::kTypeMismatch, - "Expecting an object for trait '%s'", - trait_name.c_str()); - return false; - } - // Iterate over command definitions within the current trait. - for (base::DictionaryValue::Iterator command_iter(*trait_def); - !command_iter.IsAtEnd(); command_iter.Advance()) { - std::string command_name = command_iter.key(); - if (command_name.empty()) { - Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, - errors::commands::kInvalidCommandName, - "Unnamed command encountered in trait '%s'", - trait_name.c_str()); - return false; - } - const base::DictionaryValue* command_def_json = nullptr; - if (!command_iter.value().GetAsDictionary(&command_def_json)) { - Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, - errors::commands::kTypeMismatch, - "Expecting an object for command '%s'", - command_name.c_str()); - return false; - } - - // Construct the compound command name as "trait_name.cmd_name". - std::string full_command_name = Join(".", trait_name, command_name); - - // Validate the 'minimalRole' value if present. That's the only thing we - // care about so far. - std::string value; - if (!command_def_json->GetString(commands::attributes::kCommand_Role, - &value)) { - Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, - errors::commands::kInvalidMinimalRole, - "Missing '%s' attribute for command '%s'", - commands::attributes::kCommand_Role, - full_command_name.c_str()); - return false; - } - UserRole minimal_role; - if (!StringToEnum(value, &minimal_role)) { - Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, - errors::commands::kInvalidMinimalRole, - "Invalid role '%s' for command '%s'", value.c_str(), - full_command_name.c_str()); - return false; - } - // Check if we already have this command defined. - CHECK(!definitions_.Get(full_command_name, nullptr)) - << "Definition for command '" << full_command_name - << "' overrides an earlier definition"; - definitions_.Set(full_command_name, command_def_json->DeepCopy()); - } - } - return true; -} - -const base::DictionaryValue& CommandDictionary::GetCommandsAsJson() const { - return definitions_; -} - -size_t CommandDictionary::GetSize() const { - size_t size = 0; - base::DictionaryValue::Iterator trait_iter(definitions_); - while (!trait_iter.IsAtEnd()) { - std::string trait_name = trait_iter.key(); - const base::DictionaryValue* trait_def = nullptr; - CHECK(trait_iter.value().GetAsDictionary(&trait_def)); - size += trait_def->size(); - trait_iter.Advance(); - } - return size; -} - -const base::DictionaryValue* CommandDictionary::FindCommand( - const std::string& command_name) const { - const base::DictionaryValue* definition = nullptr; - // Make sure the |command_name| came in form of trait_name.command_name. - // For this, we just verify it has a single period in its name. - if (std::count(command_name.begin(), command_name.end(), '.') != 1) - return definition; - definitions_.GetDictionary(command_name, &definition); - return definition; -} - -void CommandDictionary::Clear() { - definitions_.Clear(); -} - -bool CommandDictionary::GetMinimalRole(const std::string& command_name, - UserRole* minimal_role, - ErrorPtr* error) const { - const base::DictionaryValue* command_def = FindCommand(command_name); - if (!command_def) { - Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, - errors::commands::kInvalidCommandName, - "Command definition for '%s' not found", - command_name.c_str()); - return false; - } - std::string value; - // The JSON definition has been pre-validated already in LoadCommands, so - // just using CHECKs here. - CHECK(command_def->GetString(commands::attributes::kCommand_Role, &value)); - CHECK(StringToEnum(value, minimal_role)); - return true; -} - -} // namespace weave
diff --git a/src/commands/command_dictionary.h b/src/commands/command_dictionary.h deleted file mode 100644 index 12f7e40..0000000 --- a/src/commands/command_dictionary.h +++ /dev/null
@@ -1,68 +0,0 @@ -// 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. - -#ifndef LIBWEAVE_SRC_COMMANDS_COMMAND_DICTIONARY_H_ -#define LIBWEAVE_SRC_COMMANDS_COMMAND_DICTIONARY_H_ - -#include <map> -#include <memory> -#include <set> -#include <string> -#include <vector> - -#include <base/macros.h> -#include <base/values.h> -#include <weave/error.h> - -namespace weave { - -enum class UserRole { - kViewer, - kUser, - kManager, - kOwner, -}; - -// CommandDictionary is a wrapper around a container of command definition -// schema. The command name is a compound name in a form of -// "trait_name.command_name", where "trait_name" is a name of command trait such -// as "base", "onOff", and others. So the full command name could be -// "base.reboot", for example. -class CommandDictionary final { - public: - CommandDictionary() = default; - - // Loads command definitions from a JSON object. This is done at the daemon - // startup and whenever a device daemon decides to update its command list. - // |json| is a JSON dictionary that describes the complete commands. Optional - // Returns false on failure and |error| provides additional error information - // when provided. - bool LoadCommands(const base::DictionaryValue& json, - ErrorPtr* error); - // Converts all the command definitions to a JSON object for CDD/Device - // draft. - const base::DictionaryValue& GetCommandsAsJson() const; - // Returns the number of command definitions in the dictionary. - size_t GetSize() const; - // Checks if the dictionary has no command definitions. - bool IsEmpty() const { return definitions_.empty(); } - // Remove all the command definitions from the dictionary. - void Clear(); - // Finds a definition for the given command. - const base::DictionaryValue* FindCommand( - const std::string& command_name) const; - // Determines the minimal role for the given command. Returns false if the - // command with given name is not found. - bool GetMinimalRole(const std::string& command_name, - UserRole* minimal_role, - ErrorPtr* error) const; - - private: - base::DictionaryValue definitions_; // List of all available command defs. - DISALLOW_COPY_AND_ASSIGN(CommandDictionary); -}; - -} // namespace weave - -#endif // LIBWEAVE_SRC_COMMANDS_COMMAND_DICTIONARY_H_
diff --git a/src/commands/command_dictionary_unittest.cc b/src/commands/command_dictionary_unittest.cc deleted file mode 100644 index 7c53935..0000000 --- a/src/commands/command_dictionary_unittest.cc +++ /dev/null
@@ -1,159 +0,0 @@ -// 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_dictionary.h" - -#include <gtest/gtest.h> -#include <weave/test/unittest_utils.h> - -namespace weave { - -using test::CreateDictionaryValue; -using test::IsEqualValue; - -TEST(CommandDictionary, Empty) { - CommandDictionary dict; - EXPECT_TRUE(dict.IsEmpty()); - EXPECT_EQ(nullptr, dict.FindCommand("robot.jump")); -} - -TEST(CommandDictionary, LoadCommands) { - auto json = CreateDictionaryValue(R"({ - 'robot': { - 'jump': { - 'minimalRole': 'manager', - 'parameters': { - 'height': 'integer', - '_jumpType': ['_withAirFlip', '_withSpin', '_withKick'] - }, - 'progress': { - 'progress': 'integer' - }, - 'results': {} - } - } - })"); - CommandDictionary dict; - EXPECT_TRUE(dict.LoadCommands(*json, nullptr)); - EXPECT_EQ(1u, dict.GetSize()); - EXPECT_NE(nullptr, dict.FindCommand("robot.jump")); - json = CreateDictionaryValue(R"({ - 'base': { - 'reboot': { - 'minimalRole': 'owner', - 'parameters': {'delay': 'integer'} - }, - 'shutdown': { - 'minimalRole': 'user' - } - } - })"); - EXPECT_TRUE(dict.LoadCommands(*json, nullptr)); - EXPECT_EQ(3u, dict.GetSize()); - EXPECT_NE(nullptr, dict.FindCommand("robot.jump")); - EXPECT_NE(nullptr, dict.FindCommand("base.reboot")); - EXPECT_NE(nullptr, dict.FindCommand("base.shutdown")); - EXPECT_EQ(nullptr, dict.FindCommand("foo.bar")); -} - -TEST(CommandDictionary, LoadCommands_Failures) { - CommandDictionary dict; - ErrorPtr error; - - // Command definition is not an object. - auto json = CreateDictionaryValue("{'robot':{'jump':0}}"); - EXPECT_FALSE(dict.LoadCommands(*json, &error)); - EXPECT_EQ("type_mismatch", error->GetCode()); - error.reset(); - - // Package definition is not an object. - json = CreateDictionaryValue("{'robot':'blah'}"); - EXPECT_FALSE(dict.LoadCommands(*json, &error)); - EXPECT_EQ("type_mismatch", error->GetCode()); - error.reset(); - - // Empty command name. - json = CreateDictionaryValue("{'robot':{'':{'parameters':{},'results':{}}}}"); - EXPECT_FALSE(dict.LoadCommands(*json, &error)); - EXPECT_EQ("invalid_command_name", error->GetCode()); - error.reset(); - - // No 'minimalRole'. - json = CreateDictionaryValue(R"({ - 'base': { - 'reboot': { - 'parameters': {'delay': 'integer'} - } - } - })"); - EXPECT_FALSE(dict.LoadCommands(*json, &error)); - EXPECT_EQ("invalid_minimal_role", error->GetCode()); - error.reset(); - - // Invalid 'minimalRole'. - json = CreateDictionaryValue(R"({ - 'base': { - 'reboot': { - 'minimalRole': 'foo', - 'parameters': {'delay': 'integer'} - } - } - })"); - EXPECT_FALSE(dict.LoadCommands(*json, &error)); - EXPECT_EQ("invalid_minimal_role", error->GetCode()); - error.reset(); -} - -TEST(CommandDictionaryDeathTest, LoadCommands_Redefine) { - // Redefine commands. - CommandDictionary dict; - ErrorPtr error; - auto json = - CreateDictionaryValue("{'robot':{'jump':{'minimalRole': 'viewer'}}}"); - dict.LoadCommands(*json, nullptr); - ASSERT_DEATH(dict.LoadCommands(*json, &error), - ".*Definition for command 'robot.jump' overrides an " - "earlier definition"); -} - -TEST(CommandDictionary, GetMinimalRole) { - CommandDictionary base_dict; - auto json = CreateDictionaryValue(R"({ - 'base': { - 'command1': { - 'minimalRole': 'viewer', - 'parameters': {}, - 'results': {} - }, - 'command2': { - 'minimalRole': 'user', - 'parameters': {}, - 'results': {} - }, - 'command3': { - 'minimalRole': 'manager', - 'parameters': {}, - 'results': {} - }, - 'command4': { - 'minimalRole': 'owner', - 'parameters': {}, - 'results': {} - } - } - })"); - EXPECT_TRUE(base_dict.LoadCommands(*json, nullptr)); - UserRole role; - EXPECT_TRUE(base_dict.GetMinimalRole("base.command1", &role, nullptr)); - EXPECT_EQ(UserRole::kViewer, role); - EXPECT_TRUE(base_dict.GetMinimalRole("base.command2", &role, nullptr)); - EXPECT_EQ(UserRole::kUser, role); - EXPECT_TRUE(base_dict.GetMinimalRole("base.command3", &role, nullptr)); - EXPECT_EQ(UserRole::kManager, role); - EXPECT_TRUE(base_dict.GetMinimalRole("base.command4", &role, nullptr)); - EXPECT_EQ(UserRole::kOwner, role); - EXPECT_FALSE(base_dict.GetMinimalRole("base.command5", &role, nullptr)); -} - -} // namespace weave
diff --git a/src/commands/command_instance.cc b/src/commands/command_instance.cc index 702a819..da62887 100644 --- a/src/commands/command_instance.cc +++ b/src/commands/command_instance.cc
@@ -9,7 +9,6 @@ #include <weave/error.h> #include <weave/export.h> -#include "src/commands/command_dictionary.h" #include "src/commands/command_queue.h" #include "src/commands/schema_constants.h" #include "src/json_error_codes.h"
diff --git a/src/commands/command_manager.cc b/src/commands/command_manager.cc deleted file mode 100644 index 9e9852b..0000000 --- a/src/commands/command_manager.cc +++ /dev/null
@@ -1,107 +0,0 @@ -// 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_manager.h" - -#include <base/values.h> -#include <weave/enum_to_string.h> -#include <weave/error.h> - -#include "src/commands/schema_constants.h" -#include "src/utils.h" - -namespace weave { - -CommandManager::CommandManager() {} - -CommandManager::~CommandManager() {} - -void CommandManager::AddCommandDefChanged(const base::Closure& callback) { - on_command_changed_.push_back(callback); - callback.Run(); -} - -const CommandDictionary& CommandManager::GetCommandDictionary() const { - return dictionary_; -} - -bool CommandManager::LoadCommands(const base::DictionaryValue& dict, - ErrorPtr* error) { - bool result = dictionary_.LoadCommands(dict, error); - for (const auto& cb : on_command_changed_) - cb.Run(); - return result; -} - -bool CommandManager::LoadCommands(const std::string& json, - ErrorPtr* error) { - std::unique_ptr<const base::DictionaryValue> dict = LoadJsonDict(json, error); - if (!dict) - return false; - return LoadCommands(*dict, error); -} - -void CommandManager::AddCommand( - std::unique_ptr<CommandInstance> command_instance) { - command_queue_.Add(std::move(command_instance)); -} - -bool CommandManager::AddCommand(const base::DictionaryValue& command, - std::string* id, - ErrorPtr* error) { - return AddCommand(command, UserRole::kOwner, id, error); -} - -bool CommandManager::AddCommand(const base::DictionaryValue& command, - UserRole role, - std::string* id, - ErrorPtr* error) { - auto command_instance = CommandInstance::FromJson( - &command, Command::Origin::kLocal, nullptr, error); - if (!command_instance) - return false; - - UserRole minimal_role; - if (!GetCommandDictionary().GetMinimalRole(command_instance->GetName(), - &minimal_role, error)) { - return false; - } - if (role < minimal_role) { - Error::AddToPrintf( - error, FROM_HERE, errors::commands::kDomain, "access_denied", - "User role '%s' less than minimal: '%s'", EnumToString(role).c_str(), - EnumToString(minimal_role).c_str()); - return false; - } - - command_instance->SetComponent("device"); - *id = std::to_string(++next_command_id_); - command_instance->SetID(*id); - AddCommand(std::move(command_instance)); - return true; -} - -CommandInstance* CommandManager::FindCommand(const std::string& id) { - return command_queue_.Find(id); -} - -void CommandManager::AddCommandAddedCallback( - const CommandQueue::CommandCallback& callback) { - command_queue_.AddCommandAddedCallback(callback); -} - -void CommandManager::AddCommandRemovedCallback( - const CommandQueue::CommandCallback& callback) { - command_queue_.AddCommandRemovedCallback(callback); -} - -void CommandManager::AddCommandHandler( - const std::string& command_name, - const Device::CommandHandlerCallback& callback) { - CHECK(command_name.empty() || dictionary_.FindCommand(command_name)) - << "Command undefined: " << command_name; - command_queue_.AddCommandHandler("device", command_name, callback); -} - -} // namespace weave
diff --git a/src/commands/command_manager.h b/src/commands/command_manager.h deleted file mode 100644 index 644c165..0000000 --- a/src/commands/command_manager.h +++ /dev/null
@@ -1,75 +0,0 @@ -// 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. - -#ifndef LIBWEAVE_SRC_COMMANDS_COMMAND_MANAGER_H_ -#define LIBWEAVE_SRC_COMMANDS_COMMAND_MANAGER_H_ - -#include <memory> -#include <string> -#include <vector> - -#include <base/callback.h> -#include <base/macros.h> -#include <base/memory/weak_ptr.h> - -#include "src/commands/command_dictionary.h" -#include "src/commands/command_queue.h" - -namespace weave { - -class CommandInstance; - -// CommandManager class that will have a list of all the device command -// schemas as well as the live command queue of pending command instances -// dispatched to the device. -class CommandManager final { - public: - CommandManager(); - - ~CommandManager(); - - bool AddCommand(const base::DictionaryValue& command, - std::string* id, - ErrorPtr* error); - CommandInstance* FindCommand(const std::string& id); - void AddCommandAddedCallback(const CommandQueue::CommandCallback& callback); - void AddCommandRemovedCallback(const CommandQueue::CommandCallback& callback); - void AddCommandHandler(const std::string& command_name, - const Device::CommandHandlerCallback& callback); - - // Sets callback which is called when command definitions is changed. - void AddCommandDefChanged(const base::Closure& callback); - - // Returns the command definitions for the device. - const CommandDictionary& GetCommandDictionary() const; - - // Loads device command schema. - bool LoadCommands(const base::DictionaryValue& dict, - ErrorPtr* error); - - // Same as the overload above, but takes a path to a json file to read - // the base command definitions from. - bool LoadCommands(const std::string& json, - ErrorPtr* error); - - // Adds a new command to the command queue. - void AddCommand(std::unique_ptr<CommandInstance> command_instance); - - bool AddCommand(const base::DictionaryValue& command, - UserRole role, - std::string* id, - ErrorPtr* error); - - private: - CommandDictionary dictionary_; // Registered definitions. - CommandQueue command_queue_; - std::vector<base::Callback<void()>> on_command_changed_; - uint32_t next_command_id_{0}; - - DISALLOW_COPY_AND_ASSIGN(CommandManager); -}; - -} // namespace weave - -#endif // LIBWEAVE_SRC_COMMANDS_COMMAND_MANAGER_H_
diff --git a/src/commands/command_manager_unittest.cc b/src/commands/command_manager_unittest.cc deleted file mode 100644 index 303cafa..0000000 --- a/src/commands/command_manager_unittest.cc +++ /dev/null
@@ -1,99 +0,0 @@ -// 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_manager.h" - -#include <map> - -#include <base/json/json_writer.h> -#include <gtest/gtest.h> -#include <weave/provider/test/mock_config_store.h> -#include <weave/test/unittest_utils.h> - -#include "src/bind_lambda.h" - -using testing::Return; - -namespace weave { - -using test::CreateDictionaryValue; - -namespace { - -const char kTestVendorCommands[] = R"({ - "robot": { - "_jump": { - "minimalRole": "user", - "parameters": {"height": "integer"}, - "results": {} - }, - "_speak": { - "minimalRole": "user", - "parameters": {"phrase": "string"}, - "results": {} - } - } -})"; - -const char kTestTestCommands[] = R"({ - "test": { - "_yo": { - "minimalRole": "user", - "parameters": {"name": "string"}, - "results": {} - } - } -})"; - -} // namespace - -TEST(CommandManager, Empty) { - CommandManager manager; - EXPECT_TRUE(manager.GetCommandDictionary().IsEmpty()); -} - -TEST(CommandManager, LoadCommandsDict) { - CommandManager manager; - auto json = CreateDictionaryValue(kTestVendorCommands); - EXPECT_TRUE(manager.LoadCommands(*json, nullptr)); -} - -TEST(CommandManager, LoadCommandsJson) { - CommandManager manager; - - // Load device-supported commands. - auto json_str = R"({ - "base": { - "reboot": { - "minimalRole": "user", - "parameters": {"delay": "integer"}, - "results": {} - } - }, - "robot": { - "_jump": { - "minimalRole": "user", - "parameters": {"height": "integer"}, - "results": {} - } - } - })"; - EXPECT_TRUE(manager.LoadCommands(json_str, nullptr)); - EXPECT_EQ(2u, manager.GetCommandDictionary().GetSize()); - EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("base.reboot")); - EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("robot._jump")); -} - -TEST(CommandManager, ShouldLoadStandardAndTestDefinitions) { - CommandManager manager; - ASSERT_TRUE(manager.LoadCommands(kTestVendorCommands, nullptr)); - ASSERT_TRUE(manager.LoadCommands(kTestTestCommands, nullptr)); - EXPECT_EQ(3u, manager.GetCommandDictionary().GetSize()); - EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("robot._jump")); - EXPECT_NE(nullptr, - manager.GetCommandDictionary().FindCommand("robot._speak")); - EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("test._yo")); -} - -} // namespace weave
diff --git a/src/component_manager.h b/src/component_manager.h index fbc18aa..5f16ac4 100644 --- a/src/component_manager.h +++ b/src/component_manager.h
@@ -13,13 +13,19 @@ #include <base/values.h> #include <weave/error.h> -#include "src/commands/command_dictionary.h" #include "src/commands/command_queue.h" namespace weave { class CommandInstance; +enum class UserRole { + kViewer, + kUser, + kManager, + kOwner, +}; + // A simple notification record event to track component state changes. // The |timestamp| records the time of the state change. // |changed_properties| contains a property set with the new property values @@ -82,13 +88,22 @@ virtual void AddComponentTreeChangedCallback( const base::Closure& callback) = 0; - // Parses the command definition from a json dictionary and adds it to the - // command queue. The new command ID is returned through optional |id| param. - virtual bool AddCommand(const base::DictionaryValue& command, - Command::Origin command_origin, - UserRole role, - std::string* id, - ErrorPtr* error) = 0; + // Adds a new command instance to the command queue. The command specified in + // |command_instance| must be fully initialized and have its name, component, + // id populated. + virtual void AddCommand( + std::unique_ptr<CommandInstance> command_instance) = 0; + + // Parses the command definition from a json dictionary. The resulting command + // instance is populated with all the required fields and partially validated + // against syntax/schema. + // The new command ID is returned through optional |id| param. + virtual std::unique_ptr<CommandInstance> ParseCommandInstance( + const base::DictionaryValue& command, + Command::Origin command_origin, + UserRole role, + std::string* id, + ErrorPtr* error) = 0; // Find a command instance with the given ID in the command queue. virtual CommandInstance* FindCommand(const std::string& id) = 0;
diff --git a/src/component_manager_impl.cc b/src/component_manager_impl.cc index 1883739..5b0ab23 100644 --- a/src/component_manager_impl.cc +++ b/src/component_manager_impl.cc
@@ -18,7 +18,18 @@ namespace { // Max of 100 state update events should be enough in the queue. const size_t kMaxStateChangeQueueSize = 100; -} // namespace + +const EnumToStringMap<UserRole>::Map kMap[] = { + {UserRole::kViewer, commands::attributes::kCommand_Role_Viewer}, + {UserRole::kUser, commands::attributes::kCommand_Role_User}, + {UserRole::kOwner, commands::attributes::kCommand_Role_Owner}, + {UserRole::kManager, commands::attributes::kCommand_Role_Manager}, +}; +} // anonymous namespace + +template <> +LIBWEAVE_EXPORT EnumToStringMap<UserRole>::EnumToStringMap() + : EnumToStringMap(kMap) {} ComponentManagerImpl::ComponentManagerImpl() {} @@ -147,11 +158,17 @@ callback.Run(); } -bool ComponentManagerImpl::AddCommand(const base::DictionaryValue& command, - Command::Origin command_origin, - UserRole role, - std::string* id, - ErrorPtr* error) { +void ComponentManagerImpl::AddCommand( + std::unique_ptr<CommandInstance> command_instance) { + command_queue_.Add(std::move(command_instance)); +} + +std::unique_ptr<CommandInstance> ComponentManagerImpl::ParseCommandInstance( + const base::DictionaryValue& command, + Command::Origin command_origin, + UserRole role, + std::string* id, + ErrorPtr* error) { std::string command_id; auto command_instance = CommandInstance::FromJson(&command, command_origin, &command_id, error); @@ -162,18 +179,18 @@ *id = command_id; if (!command_instance) - return false; + return nullptr; UserRole minimal_role; if (!GetMinimalRole(command_instance->GetName(), &minimal_role, error)) - return false; + return nullptr; if (role < minimal_role) { Error::AddToPrintf( error, FROM_HERE, errors::commands::kDomain, "access_denied", "User role '%s' less than minimal: '%s'", EnumToString(role).c_str(), EnumToString(minimal_role).c_str()); - return false; + return nullptr; } std::string component_path = command_instance->GetComponent(); @@ -188,14 +205,14 @@ "Unable route command '%s' because there is no component supporting" "trait '%s'", command_instance->GetName().c_str(), trait_name.c_str()); - return false; + return nullptr; } command_instance->SetComponent(component_path); } const base::DictionaryValue* component = FindComponent(component_path, error); if (!component) - return false; + return nullptr; // Check that the command's trait is supported by the given component. auto pair = SplitAtFirst(command_instance->GetName(), ".", true); @@ -218,18 +235,17 @@ "trait_not_supported", "Component '%s' doesn't support trait '%s'", component_path.c_str(), pair.first.c_str()); - return false; + return nullptr; } if (command_id.empty()) { command_id = std::to_string(++next_command_id_); command_instance->SetID(command_id); + if (id) + *id = command_id; } - if (id) - *id = command_id; - command_queue_.Add(std::move(command_instance)); - return true; + return command_instance; } CommandInstance* ComponentManagerImpl::FindCommand(const std::string& id) {
diff --git a/src/component_manager_impl.h b/src/component_manager_impl.h index 9778a93..c59c6d9 100644 --- a/src/component_manager_impl.h +++ b/src/component_manager_impl.h
@@ -49,13 +49,22 @@ // Sets callback which is called when new components are added. void AddComponentTreeChangedCallback(const base::Closure& callback) override; - // Parses the command definition from a json dictionary and adds it to the - // command queue. The new command ID is returned through optional |id| param. - bool AddCommand(const base::DictionaryValue& command, - Command::Origin command_origin, - UserRole role, - std::string* id, - ErrorPtr* error) override; + // Adds a new command instance to the command queue. The command specified in + // |command_instance| must be fully initialized and have its name, component, + // id populated. + void AddCommand( + std::unique_ptr<CommandInstance> command_instance) override; + + // Parses the command definition from a json dictionary. The resulting command + // instance is populated with all the required fields and partially validated + // against syntax/schema. + // The new command ID is returned through optional |id| param. + std::unique_ptr<CommandInstance> ParseCommandInstance( + const base::DictionaryValue& command, + Command::Origin command_origin, + UserRole role, + std::string* id, + ErrorPtr* error) override; // Find a command instance with the given ID in the command queue. CommandInstance* FindCommand(const std::string& id) override; @@ -180,6 +189,13 @@ ErrorPtr* error); base::Clock* clock_{nullptr}; + // An ID of last state change update. Each NotifyPropertiesUpdated() + // invocation increments this value by 1. + UpdateID last_state_change_id_{0}; + // Callback list for state change queue event sinks. + // This member must be defined before |command_queue_|. + base::CallbackList<void(UpdateID)> on_server_state_updated_; + base::DictionaryValue traits_; // Trait definitions. base::DictionaryValue components_; // Component instances. CommandQueue command_queue_; // Command queue containing command instances. @@ -187,13 +203,7 @@ std::vector<base::Closure> on_componet_tree_changed_; std::vector<base::Closure> on_state_changed_; uint32_t next_command_id_{0}; - std::map<std::string, std::unique_ptr<StateChangeQueue>> state_change_queues_; - // An ID of last state change update. Each NotifyPropertiesUpdated() - // invocation increments this value by 1. - UpdateID last_state_change_id_{0}; - // Callback list for state change queue event sinks. - base::CallbackList<void(UpdateID)> on_server_state_updated_; // Legacy API support. mutable base::DictionaryValue legacy_state_; // Device state.
diff --git a/src/component_manager_unittest.cc b/src/component_manager_unittest.cc index 8c9b386..b35e16a 100644 --- a/src/component_manager_unittest.cc +++ b/src/component_manager_unittest.cc
@@ -621,7 +621,7 @@ EXPECT_EQ(nullptr, manager.FindComponent("comp1.comp2[1", nullptr)); } -TEST(ComponentManager, AddCommand) { +TEST(ComponentManager, ParseCommandInstance) { ComponentManagerImpl manager; const char kTraits[] = R"({ "trait1": { @@ -635,6 +635,12 @@ "command1": { "minimalRole": "manager" }, "command2": { "minimalRole": "owner" } } + }, + "trait3": { + "commands": { + "command1": { "minimalRole": "manager" }, + "command2": { "minimalRole": "owner" } + } } })"; auto traits = CreateDictionaryValue(kTraits); @@ -650,12 +656,14 @@ "parameters": {} })"; auto command1 = CreateDictionaryValue(kCommand1); - EXPECT_TRUE(manager.AddCommand(*command1, Command::Origin::kLocal, - UserRole::kUser, &id, nullptr)); + EXPECT_NE(nullptr, + manager.ParseCommandInstance(*command1, Command::Origin::kLocal, + UserRole::kUser, &id, nullptr).get()); EXPECT_EQ("1234-12345", id); // Not enough access rights - EXPECT_FALSE(manager.AddCommand(*command1, Command::Origin::kLocal, - UserRole::kViewer, &id, nullptr)); + EXPECT_EQ(nullptr, + manager.ParseCommandInstance(*command1, Command::Origin::kLocal, + UserRole::kViewer, &id, nullptr).get()); const char kCommand2[] = R"({ "name": "trait1.command3", @@ -664,8 +672,9 @@ })"; auto command2 = CreateDictionaryValue(kCommand2); // trait1.command3 doesn't exist - EXPECT_FALSE(manager.AddCommand(*command2, Command::Origin::kLocal, - UserRole::kOwner, &id, nullptr)); + EXPECT_EQ(nullptr, + manager.ParseCommandInstance(*command2, Command::Origin::kLocal, + UserRole::kOwner, &id, nullptr).get()); EXPECT_TRUE(id.empty()); const char kCommand3[] = R"({ @@ -675,8 +684,9 @@ })"; auto command3 = CreateDictionaryValue(kCommand3); // Component comp1 doesn't have trait2. - EXPECT_FALSE(manager.AddCommand(*command3, Command::Origin::kLocal, - UserRole::kOwner, &id, nullptr)); + EXPECT_EQ(nullptr, + manager.ParseCommandInstance(*command3, Command::Origin::kLocal, + UserRole::kOwner, &id, nullptr).get()); // No component specified, find the suitable component const char kCommand4[] = R"({ @@ -684,22 +694,60 @@ "parameters": {} })"; auto command4 = CreateDictionaryValue(kCommand4); - EXPECT_TRUE(manager.AddCommand(*command4, Command::Origin::kLocal, - UserRole::kOwner, &id, nullptr)); - auto cmd = manager.FindCommand(id); - ASSERT_NE(nullptr, cmd); - EXPECT_EQ("comp1", cmd->GetComponent()); + auto command_instance = manager.ParseCommandInstance( + *command4, Command::Origin::kLocal, UserRole::kOwner, &id, nullptr); + EXPECT_NE(nullptr, command_instance.get()); + EXPECT_EQ("comp1", command_instance->GetComponent()); const char kCommand5[] = R"({ "name": "trait2.command1", "parameters": {} })"; auto command5 = CreateDictionaryValue(kCommand5); - EXPECT_TRUE(manager.AddCommand(*command5, Command::Origin::kLocal, - UserRole::kOwner, &id, nullptr)); - cmd = manager.FindCommand(id); - ASSERT_NE(nullptr, cmd); - EXPECT_EQ("comp2", cmd->GetComponent()); + command_instance = manager.ParseCommandInstance( + *command5, Command::Origin::kLocal, UserRole::kOwner, &id, nullptr); + EXPECT_NE(nullptr, command_instance.get()); + EXPECT_EQ("comp2", command_instance->GetComponent()); + + // Cannot route the command, no component with 'trait3'. + const char kCommand6[] = R"({ + "name": "trait3.command1", + "parameters": {} + })"; + auto command6 = CreateDictionaryValue(kCommand6); + EXPECT_EQ(nullptr, + manager.ParseCommandInstance(*command6, Command::Origin::kLocal, + UserRole::kOwner, &id, nullptr).get()); +} + +TEST(ComponentManager, AddCommand) { + ComponentManagerImpl manager; + const char kTraits[] = R"({ + "trait1": { + "commands": { + "command1": { "minimalRole": "user" } + } + } + })"; + auto traits = CreateDictionaryValue(kTraits); + ASSERT_TRUE(manager.LoadTraits(*traits, nullptr)); + ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1"}, nullptr)); + + std::string id; + const char kCommand[] = R"({ + "name": "trait1.command1", + "id": "1234-12345", + "component": "comp1", + "parameters": {} + })"; + auto command = CreateDictionaryValue(kCommand); + auto command_instance = manager.ParseCommandInstance( + *command, Command::Origin::kLocal, UserRole::kUser, &id, nullptr); + ASSERT_NE(nullptr, command_instance.get()); + manager.AddCommand(std::move(command_instance)); + const auto* queued_command = manager.FindCommand(id); + ASSERT_NE(nullptr, queued_command); + EXPECT_EQ("trait1.command1", queued_command->GetName()); } TEST(ComponentManager, AddCommandHandler) { @@ -738,8 +786,10 @@ "component": "comp1" })"; auto command1 = CreateDictionaryValue(kCommand1); - EXPECT_TRUE(manager.AddCommand(*command1, Command::Origin::kCloud, - UserRole::kUser, nullptr, nullptr)); + auto command_instance = manager.ParseCommandInstance( + *command1, Command::Origin::kCloud, UserRole::kUser, nullptr, nullptr); + ASSERT_NE(nullptr, command_instance.get()); + manager.AddCommand(std::move(command_instance)); EXPECT_EQ("1", last_tags); last_tags.clear(); @@ -748,8 +798,10 @@ "component": "comp2" })"; auto command2 = CreateDictionaryValue(kCommand2); - EXPECT_TRUE(manager.AddCommand(*command2, Command::Origin::kCloud, - UserRole::kUser, nullptr, nullptr)); + command_instance = manager.ParseCommandInstance( + *command2, Command::Origin::kCloud, UserRole::kUser, nullptr, nullptr); + ASSERT_NE(nullptr, command_instance.get()); + manager.AddCommand(std::move(command_instance)); EXPECT_EQ("2", last_tags); last_tags.clear(); @@ -759,8 +811,10 @@ "parameters": {} })"; auto command3 = CreateDictionaryValue(kCommand3); - EXPECT_TRUE(manager.AddCommand(*command3, Command::Origin::kLocal, - UserRole::kUser, nullptr, nullptr)); + command_instance = manager.ParseCommandInstance( + *command3, Command::Origin::kCloud, UserRole::kUser, nullptr, nullptr); + ASSERT_NE(nullptr, command_instance.get()); + manager.AddCommand(std::move(command_instance)); EXPECT_EQ("3", last_tags); last_tags.clear(); }
diff --git a/src/device_manager.cc b/src/device_manager.cc index a44f0db..7a9021f 100644 --- a/src/device_manager.cc +++ b/src/device_manager.cc
@@ -9,23 +9,16 @@ #include <base/bind.h> #include "src/base_api_handler.h" -#include "src/commands/command_manager.h" +#include "src/commands/schema_constants.h" #include "src/component_manager_impl.h" #include "src/config.h" #include "src/device_registration_info.h" #include "src/privet/privet_manager.h" -#include "src/states/state_change_queue.h" -#include "src/states/state_manager.h" +#include "src/string_utils.h" +#include "src/utils.h" namespace weave { -namespace { - -// Max of 100 state update events should be enough in the queue. -const size_t kMaxStateChangeQueueSize = 100; - -} // namespace - DeviceManager::DeviceManager(provider::ConfigStore* config_store, provider::TaskRunner* task_runner, provider::HttpClient* http_client, @@ -35,16 +28,13 @@ provider::Wifi* wifi, provider::Bluetooth* bluetooth) { component_manager_.reset(new ComponentManagerImpl); - command_manager_ = std::make_shared<CommandManager>(); - state_change_queue_.reset(new StateChangeQueue(kMaxStateChangeQueueSize)); - state_manager_ = std::make_shared<StateManager>(state_change_queue_.get()); std::unique_ptr<Config> config{new Config{config_store}}; config->Load(); device_info_.reset(new DeviceRegistrationInfo( - command_manager_, state_manager_, std::move(config), task_runner, - http_client, network)); + component_manager_.get(), std::move(config), task_runner, http_client, + network)); base_api_handler_.reset(new BaseApiHandler{device_info_.get(), this}); device_info_->Start(); @@ -79,7 +69,7 @@ provider::Bluetooth* bluetooth) { privet_.reset(new privet::Manager{task_runner}); privet_->Start(network, dns_sd, http_server, wifi, device_info_.get(), - command_manager_.get(), state_manager_.get()); + component_manager_.get()); } GcdState DeviceManager::GetGcdState() const { @@ -151,63 +141,108 @@ } void DeviceManager::AddCommandDefinitionsFromJson(const std::string& json) { - CHECK(command_manager_->LoadCommands(json, nullptr)); + auto dict = LoadJsonDict(json, nullptr); + CHECK(dict); + AddCommandDefinitions(*dict); } void DeviceManager::AddCommandDefinitions(const base::DictionaryValue& dict) { - CHECK(command_manager_->LoadCommands(dict, nullptr)); + CHECK(component_manager_->AddLegacyCommandDefinitions(dict, nullptr)); } bool DeviceManager::AddCommand(const base::DictionaryValue& command, std::string* id, ErrorPtr* error) { - return command_manager_->AddCommand(command, id, error); + auto command_instance = + component_manager_->ParseCommandInstance(command, Command::Origin::kLocal, + UserRole::kOwner, id, error); + if (!command_instance) + return false; + component_manager_->AddCommand(std::move(command_instance)); + return true; } Command* DeviceManager::FindCommand(const std::string& id) { - return command_manager_->FindCommand(id); + return component_manager_->FindCommand(id); } void DeviceManager::AddCommandHandler(const std::string& command_name, const CommandHandlerCallback& callback) { - return command_manager_->AddCommandHandler(command_name, callback); + if (command_name.empty()) + return component_manager_->AddCommandHandler("", "", callback); + + auto trait = SplitAtFirst(command_name, ".", true).first; + std::string component = component_manager_->FindComponentWithTrait(trait); + CHECK(!component.empty()); + component_manager_->AddCommandHandler(component, command_name, callback); } void DeviceManager::AddStateChangedCallback(const base::Closure& callback) { - state_manager_->AddChangedCallback(callback); + component_manager_->AddStateChangedCallback(callback); } void DeviceManager::AddStateDefinitionsFromJson(const std::string& json) { - CHECK(state_manager_->LoadStateDefinitionFromJson(json, nullptr)); + auto dict = LoadJsonDict(json, nullptr); + CHECK(dict); + AddStateDefinitions(*dict); } void DeviceManager::AddStateDefinitions(const base::DictionaryValue& dict) { - CHECK(state_manager_->LoadStateDefinition(dict, nullptr)); + CHECK(component_manager_->AddLegacyStateDefinitions(dict, nullptr)); } bool DeviceManager::SetStatePropertiesFromJson(const std::string& json, ErrorPtr* error) { - return state_manager_->SetPropertiesFromJson(json, error); + auto dict = LoadJsonDict(json, error); + return dict && SetStateProperties(*dict, error); } bool DeviceManager::SetStateProperties(const base::DictionaryValue& dict, ErrorPtr* error) { - return state_manager_->SetProperties(dict, error); + for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) { + std::string component = + component_manager_->FindComponentWithTrait(it.key()); + if (component.empty()) { + Error::AddToPrintf( + error, FROM_HERE, errors::commands::kDomain, "unrouted_state", + "Unable to set property value because there is no component supporting " + "trait '%s'", it.key().c_str()); + return false; + } + base::DictionaryValue trait_state; + trait_state.Set(it.key(), it.value().DeepCopy()); + if (!component_manager_->SetStateProperties(component, trait_state, error)) + return false; + } + return true; } const base::Value* DeviceManager::GetStateProperty( const std::string& name) const { - return state_manager_->GetProperty(name); + auto trait = SplitAtFirst(name, ".", true).first; + std::string component = component_manager_->FindComponentWithTrait(trait); + if (component.empty()) + return nullptr; + return component_manager_->GetStateProperty(component, name, nullptr); } bool DeviceManager::SetStateProperty(const std::string& name, const base::Value& value, ErrorPtr* error) { - return state_manager_->SetProperty(name, value, error); + auto trait = SplitAtFirst(name, ".", true).first; + std::string component = component_manager_->FindComponentWithTrait(trait); + if (component.empty()) { + Error::AddToPrintf( + error, FROM_HERE, errors::commands::kDomain, "unrouted_state", + "Unable set value of state property '%s' because there is no component " + "supporting trait '%s'", name.c_str(), trait.c_str()); + return false; + } + return component_manager_->SetStateProperty(component, name, value, error); } const base::DictionaryValue& DeviceManager::GetState() const { - return state_manager_->GetState(); + return component_manager_->GetLegacyState(); } void DeviceManager::Register(const std::string& ticket_id,
diff --git a/src/device_manager.h b/src/device_manager.h index 2a23755..3b042eb 100644 --- a/src/device_manager.h +++ b/src/device_manager.h
@@ -12,11 +12,8 @@ class BaseApiHandler; class Config; -class CommandManager; class ComponentManager; class DeviceRegistrationInfo; -class StateChangeQueue; -class StateManager; namespace privet { class Manager; @@ -103,9 +100,6 @@ provider::Bluetooth* bluetooth); std::unique_ptr<ComponentManager> component_manager_; - std::shared_ptr<CommandManager> command_manager_; - std::unique_ptr<StateChangeQueue> state_change_queue_; - std::shared_ptr<StateManager> state_manager_; std::unique_ptr<DeviceRegistrationInfo> device_info_; std::unique_ptr<BaseApiHandler> base_api_handler_; std::unique_ptr<privet::Manager> privet_;
diff --git a/src/device_registration_info.cc b/src/device_registration_info.cc index 9469d09..abdbb08 100644 --- a/src/device_registration_info.cc +++ b/src/device_registration_info.cc
@@ -22,13 +22,11 @@ #include "src/bind_lambda.h" #include "src/commands/cloud_command_proxy.h" -#include "src/commands/command_manager.h" #include "src/commands/schema_constants.h" #include "src/data_encoding.h" #include "src/http_constants.h" #include "src/json_error_codes.h" #include "src/notification/xmpp_channel.h" -#include "src/states/state_manager.h" #include "src/string_utils.h" #include "src/utils.h" @@ -239,16 +237,14 @@ } // anonymous namespace DeviceRegistrationInfo::DeviceRegistrationInfo( - const std::shared_ptr<CommandManager>& command_manager, - const std::shared_ptr<StateManager>& state_manager, + ComponentManager* component_manager, std::unique_ptr<Config> config, provider::TaskRunner* task_runner, provider::HttpClient* http_client, provider::Network* network) : http_client_{http_client}, task_runner_{task_runner}, - command_manager_{command_manager}, - state_manager_{state_manager}, + component_manager_{component_manager}, config_{std::move(config)}, network_{network} { cloud_backoff_policy_.reset(new BackoffEntry::Policy{}); @@ -267,10 +263,13 @@ gcd_state_ = revoked ? GcdState::kInvalidCredentials : GcdState::kUnconfigured; - command_manager_->AddCommandDefChanged( - base::Bind(&DeviceRegistrationInfo::OnCommandDefsChanged, + component_manager_->AddTraitDefChangedCallback( + base::Bind(&DeviceRegistrationInfo::OnTraitDefsChanged, weak_factory_.GetWeakPtr())); - state_manager_->AddChangedCallback(base::Bind( + component_manager_->AddComponentTreeChangedCallback(base::Bind( + &DeviceRegistrationInfo::OnComponentTreeChanged, + weak_factory_.GetWeakPtr())); + component_manager_->AddStateChangedCallback(base::Bind( &DeviceRegistrationInfo::OnStateChanged, weak_factory_.GetWeakPtr())); } @@ -479,12 +478,6 @@ std::unique_ptr<base::DictionaryValue> DeviceRegistrationInfo::BuildDeviceResource(ErrorPtr* error) { - // Limit only to commands that are visible to the cloud. - const base::DictionaryValue& commands = - command_manager_->GetCommandDictionary().GetCommandsAsJson(); - - const base::DictionaryValue& state = state_manager_->GetState(); - std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue}; if (!GetSettings().cloud_id.empty()) resource->SetString("id", GetSettings().cloud_id); @@ -503,34 +496,12 @@ channel->SetString("supportedType", "pull"); } resource->Set("channel", channel.release()); - resource->Set("commandDefs", commands.DeepCopy()); - resource->Set("state", state.DeepCopy()); + resource->Set("commandDefs", + component_manager_->GetLegacyCommandDefinitions().DeepCopy()); + resource->Set("state", component_manager_->GetLegacyState().DeepCopy()); + resource->Set("traits", component_manager_->GetTraits().DeepCopy()); + resource->Set("components", component_manager_->GetComponents().DeepCopy()); - // TODO(avakulenko): Temporary code to generate a single top-level component - // using the new component-trait model. This is to unblock clients to start - // implementation on their side. - base::DictionaryValue traits; - base::DictionaryValue component; - std::set<std::string> all_traits; - for (const auto& pair : state_manager_->packages()) { - all_traits.insert(pair.first); - std::string key = base::StringPrintf("%s.state", pair.first.c_str()); - traits.Set(key, pair.second->types().DeepCopy()); - key = base::StringPrintf("state.%s", pair.first.c_str()); - component.Set(key, pair.second->GetValuesAsJson().DeepCopy()); - } - for (base::DictionaryValue::Iterator it(commands); !it.IsAtEnd(); - it.Advance()) { - all_traits.insert(it.key()); - std::string key = base::StringPrintf("%s.commands", it.key().c_str()); - traits.Set(key, it.value().DeepCopy()); - } - resource->Set("traits", traits.DeepCopy()); - base::ListValue* trait_list = new base::ListValue(); - for (const std::string& trait : all_traits) - trait_list->AppendString(trait); - component.Set("traits", trait_list); - resource->Set("components.device", component.DeepCopy()); return resource; } @@ -1102,8 +1073,8 @@ const base::DictionaryValue& command) { std::string command_id; ErrorPtr error; - auto command_instance = CommandInstance::FromJson( - &command, Command::Origin::kCloud, &command_id, &error); + auto command_instance = component_manager_->ParseCommandInstance( + command, Command::Origin::kCloud, UserRole::kOwner, &command_id, &error); if (!command_instance) { LOG(WARNING) << "Failed to parse a command instance: " << command; if (!command_id.empty()) @@ -1112,19 +1083,19 @@ } // TODO(antonm): Properly process cancellation of commands. - if (!command_manager_->FindCommand(command_instance->GetID())) { + if (!component_manager_->FindCommand(command_instance->GetID())) { LOG(INFO) << "New command '" << command_instance->GetName() << "' arrived, ID: " << command_instance->GetID(); std::unique_ptr<BackoffEntry> backoff_entry{ new BackoffEntry{cloud_backoff_policy_.get()}}; std::unique_ptr<CloudCommandProxy> cloud_proxy{new CloudCommandProxy{ - command_instance.get(), this, state_manager_->GetStateChangeQueue(), + command_instance.get(), this, component_manager_, std::move(backoff_entry), task_runner_}}; // CloudCommandProxy::CloudCommandProxy() subscribe itself to Command // notifications. When Command is being destroyed it sends // ::OnCommandDestroyed() and CloudCommandProxy deletes itself. cloud_proxy.release(); - command_manager_->AddCommand(std::move(command_instance)); + component_manager_->AddCommand(std::move(command_instance)); } } @@ -1133,18 +1104,18 @@ if (device_state_update_pending_) return; - StateChangeQueueInterface::UpdateID update_id = 0; - std::vector<StateChange> state_changes; - std::tie(update_id, state_changes) = - state_manager_->GetAndClearRecordedStateChanges(); - if (state_changes.empty()) + auto snapshot = component_manager_->GetAndClearRecordedStateChanges(); + if (snapshot.state_changes.empty()) return; std::unique_ptr<base::ListValue> patches{new base::ListValue}; - for (auto& state_change : state_changes) { + for (auto& state_change : snapshot.state_changes) { std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue}; patch->SetString("timeMs", std::to_string(state_change.timestamp.ToJavaTime())); + // TODO(avakulenko): Uncomment this once server supports "component" + // attribute on a state patch object. + // patch->SetString("component", state_change.component); patch->Set("patch", state_change.changed_properties.release()); patches->Append(patch.release()); } @@ -1157,11 +1128,11 @@ device_state_update_pending_ = true; DoCloudRequest(HttpClient::Method::kPost, GetDeviceURL("patchState"), &body, base::Bind(&DeviceRegistrationInfo::OnPublishStateDone, - AsWeakPtr(), update_id)); + AsWeakPtr(), snapshot.update_id)); } void DeviceRegistrationInfo::OnPublishStateDone( - StateChangeQueueInterface::UpdateID update_id, + ComponentManager::UpdateID update_id, const base::DictionaryValue& reply, ErrorPtr error) { device_state_update_pending_ = false; @@ -1169,7 +1140,7 @@ LOG(ERROR) << "Permanent failure while trying to update device state"; return; } - state_manager_->NotifyStateUpdatedOnServer(update_id); + component_manager_->NotifyStateUpdatedOnServer(update_id); // See if there were more pending state updates since the previous request // had been sent out. PublishStateUpdates(); @@ -1183,7 +1154,7 @@ cb.Run(gcd_state_); } -void DeviceRegistrationInfo::OnCommandDefsChanged() { +void DeviceRegistrationInfo::OnTraitDefsChanged() { VLOG(1) << "CommandDefinitionChanged notification received"; if (!HaveRegistrationCredentials() || !connected_to_cloud_) return; @@ -1200,6 +1171,14 @@ PublishStateUpdates(); } +void DeviceRegistrationInfo::OnComponentTreeChanged() { + VLOG(1) << "ComponentTreeChanged notification received"; + if (!HaveRegistrationCredentials() || !connected_to_cloud_) + return; + + UpdateDeviceResource(base::Bind(&IgnoreCloudError)); +} + void DeviceRegistrationInfo::OnConnected(const std::string& channel_name) { LOG(INFO) << "Notification channel successfully established over " << channel_name;
diff --git a/src/device_registration_info.h b/src/device_registration_info.h index 1399b37..f3b5302 100644 --- a/src/device_registration_info.h +++ b/src/device_registration_info.h
@@ -21,13 +21,12 @@ #include "src/backoff_entry.h" #include "src/commands/cloud_command_update_interface.h" -#include "src/commands/command_manager.h" +#include "src/component_manager.h" #include "src/config.h" #include "src/data_encoding.h" #include "src/notification/notification_channel.h" #include "src/notification/notification_delegate.h" #include "src/notification/pull_channel.h" -#include "src/states/state_change_queue_interface.h" namespace base { class DictionaryValue; @@ -54,12 +53,12 @@ base::Callback<void(const base::DictionaryValue& response, ErrorPtr error)>; - DeviceRegistrationInfo(const std::shared_ptr<CommandManager>& command_manager, - const std::shared_ptr<StateManager>& state_manager, - std::unique_ptr<Config> config, - provider::TaskRunner* task_runner, - provider::HttpClient* http_client, - provider::Network* network); + DeviceRegistrationInfo( + ComponentManager* component_manager, + std::unique_ptr<Config> config, + provider::TaskRunner* task_runner, + provider::HttpClient* http_client, + provider::Network* network); ~DeviceRegistrationInfo() override; @@ -236,7 +235,7 @@ void FetchAndPublishCommands(const std::string& reason); void PublishStateUpdates(); - void OnPublishStateDone(StateChangeQueueInterface::UpdateID update_id, + void OnPublishStateDone(ComponentManager::UpdateID update_id, const base::DictionaryValue& reply, ErrorPtr error); void OnPublishStateError(ErrorPtr error); @@ -254,7 +253,8 @@ void SetDeviceId(const std::string& cloud_id); // Callback called when command definitions are changed to re-publish new CDD. - void OnCommandDefsChanged(); + void OnTraitDefsChanged(); + void OnComponentTreeChanged(); void OnStateChanged(); // Overrides from NotificationDelegate. @@ -299,10 +299,8 @@ provider::HttpClient* http_client_{nullptr}; provider::TaskRunner* task_runner_{nullptr}; - // Global command manager. - std::shared_ptr<CommandManager> command_manager_; - // Device state manager. - std::shared_ptr<StateManager> state_manager_; + // Global component manager. + ComponentManager* component_manager_{nullptr}; std::unique_ptr<Config> config_;
diff --git a/src/device_registration_info_unittest.cc b/src/device_registration_info_unittest.cc index f2fe4c4..c242ee5 100644 --- a/src/device_registration_info_unittest.cc +++ b/src/device_registration_info_unittest.cc
@@ -14,10 +14,8 @@ #include <weave/test/unittest_utils.h> #include "src/bind_lambda.h" -#include "src/commands/command_manager.h" +#include "src/component_manager_impl.h" #include "src/http_constants.h" -#include "src/states/mock_state_change_queue_interface.h" -#include "src/states/state_manager.h" using testing::_; using testing::AtLeast; @@ -115,17 +113,9 @@ class DeviceRegistrationInfoTest : public ::testing::Test { protected: void SetUp() override { - EXPECT_CALL(mock_state_change_queue_, GetLastStateChangeId()) - .WillRepeatedly(Return(0)); - EXPECT_CALL(mock_state_change_queue_, MockAddOnStateUpdatedCallback(_)) - .WillRepeatedly(Return(nullptr)); - - command_manager_ = std::make_shared<CommandManager>(); - state_manager_ = std::make_shared<StateManager>(&mock_state_change_queue_); - std::unique_ptr<Config> config{new Config{&config_store_}}; config_ = config.get(); - dev_reg_.reset(new DeviceRegistrationInfo{command_manager_, state_manager_, + dev_reg_.reset(new DeviceRegistrationInfo{&component_manager_, std::move(config), &task_runner_, &http_client_, nullptr}); @@ -196,9 +186,7 @@ base::DictionaryValue data_; Config* config_{nullptr}; std::unique_ptr<DeviceRegistrationInfo> dev_reg_; - std::shared_ptr<CommandManager> command_manager_; - StrictMock<MockStateChangeQueueInterface> mock_state_change_queue_; - std::shared_ptr<StateManager> state_manager_; + ComponentManagerImpl component_manager_; }; TEST_F(DeviceRegistrationInfoTest, GetServiceURL) { @@ -352,21 +340,33 @@ } TEST_F(DeviceRegistrationInfoTest, RegisterDevice) { - auto json_cmds = CreateDictionaryValue(R"({ + auto json_traits = CreateDictionaryValue(R"({ 'base': { - 'reboot': { - 'parameters': {'delay': {'minimum': 10, 'type': 'integer'}}, - 'minimalRole': 'user' + 'commands': { + 'reboot': { + 'parameters': {'delay': {'minimum': 10, 'type': 'integer'}}, + 'minimalRole': 'user' + } + }, + 'state': { + 'firmwareVersion': {'type': 'string'} } }, 'robot': { - '_jump': { - 'parameters': {'_height': {'type': 'integer'}}, - 'minimalRole': 'user' + 'commands': { + '_jump': { + 'parameters': {'_height': {'type': 'integer'}}, + 'minimalRole': 'user' + } } } })"); - EXPECT_TRUE(command_manager_->LoadCommands(*json_cmds, nullptr)); + EXPECT_TRUE(component_manager_.LoadTraits(*json_traits, nullptr)); + EXPECT_TRUE(component_manager_.AddComponent("", "comp", {"base", "robot"}, + nullptr)); + base::StringValue ver{"1.0"}; + EXPECT_TRUE(component_manager_.SetStateProperty( + "comp", "base.firmwareVersion", ver, nullptr)); std::string ticket_url = dev_reg_->GetServiceURL("registrationTickets/") + test_data::kClaimTicketId; @@ -395,12 +395,9 @@ EXPECT_EQ("AAAAA", value); EXPECT_TRUE(json->GetString("deviceDraft.name", &value)); EXPECT_EQ("Coffee Pot", value); - base::DictionaryValue* commandDefs = nullptr; - EXPECT_TRUE( - json->GetDictionary("deviceDraft.commandDefs", &commandDefs)); - EXPECT_FALSE(commandDefs->empty()); - - auto expected = R"({ + base::DictionaryValue* dict = nullptr; + EXPECT_TRUE(json->GetDictionary("deviceDraft.commandDefs", &dict)); + auto expectedCommandDefs = R"({ 'base': { 'reboot': { 'parameters': { @@ -423,7 +420,50 @@ } } })"; - EXPECT_JSON_EQ(expected, *commandDefs); + EXPECT_JSON_EQ(expectedCommandDefs, *dict); + + EXPECT_TRUE(json->GetDictionary("deviceDraft.state", &dict)); + auto expectedState = R"({ + 'base': { + 'firmwareVersion': '1.0' + } + })"; + EXPECT_JSON_EQ(expectedState, *dict); + + EXPECT_TRUE(json->GetDictionary("deviceDraft.traits", &dict)); + auto expectedTraits = R"({ + 'base': { + 'commands': { + 'reboot': { + 'parameters': {'delay': {'minimum': 10, 'type': 'integer'}}, + 'minimalRole': 'user' + } + }, + 'state': { + 'firmwareVersion': {'type': 'string'} + } + }, + 'robot': { + 'commands': { + '_jump': { + 'parameters': {'_height': {'type': 'integer'}}, + 'minimalRole': 'user' + } + } + } + })"; + EXPECT_JSON_EQ(expectedTraits, *dict); + + EXPECT_TRUE(json->GetDictionary("deviceDraft.components", &dict)); + auto expectedComponents = R"({ + 'comp': { + 'traits': ['base', 'robot'], + 'state': { + 'base': { 'firmwareVersion': '1.0' } + } + } + })"; + EXPECT_JSON_EQ(expectedComponents, *dict); base::DictionaryValue json_resp; json_resp.SetString("id", test_data::kClaimTicketId); @@ -519,22 +559,27 @@ ReloadSettings(); SetAccessToken(); - auto json_cmds = CreateDictionaryValue(R"({ + auto json_traits = CreateDictionaryValue(R"({ 'robot': { - '_jump': { - 'parameters': {'_height': 'integer'}, - 'progress': {'progress': 'integer'}, - 'results': {'status': 'string'}, - 'minimalRole': 'user' + 'commands': { + '_jump': { + 'parameters': {'_height': 'integer'}, + 'progress': {'progress': 'integer'}, + 'results': {'status': 'string'}, + 'minimalRole': 'user' + } } } })"); - EXPECT_TRUE(command_manager_->LoadCommands(*json_cmds, nullptr)); + EXPECT_TRUE(component_manager_.LoadTraits(*json_traits, nullptr)); + EXPECT_TRUE(component_manager_.AddComponent("", "comp", {"robot"}, + nullptr)); command_url_ = dev_reg_->GetServiceURL("commands/1234"); auto commands_json = CreateValue(R"([{ 'name':'robot._jump', + 'component': 'comp', 'id':'1234', 'parameters': {'_height': 100}, 'minimalRole': 'user' @@ -543,7 +588,7 @@ const base::ListValue* command_list = nullptr; ASSERT_TRUE(commands_json->GetAsList(&command_list)); PublishCommands(*command_list); - command_ = command_manager_->FindCommand("1234"); + command_ = component_manager_.FindCommand("1234"); ASSERT_NE(nullptr, command_); }
diff --git a/src/mock_component_manager.h b/src/mock_component_manager.h index aa58177..e30b8b0 100644 --- a/src/mock_component_manager.h +++ b/src/mock_component_manager.h
@@ -29,7 +29,9 @@ ErrorPtr* error)); MOCK_METHOD1(AddComponentTreeChangedCallback, void(const base::Closure& callback)); - MOCK_METHOD5(AddCommand, bool(const base::DictionaryValue& command, + MOCK_METHOD1(MockAddCommand, void(CommandInstance* command_instance)); + MOCK_METHOD5(MockParseCommandInstance, + CommandInstance*(const base::DictionaryValue& command, Command::Origin command_origin, UserRole role, std::string* id, @@ -89,6 +91,18 @@ const base::DictionaryValue&()); private: + void AddCommand(std::unique_ptr<CommandInstance> command_instance) override { + MockAddCommand(command_instance.get()); + } + std::unique_ptr<CommandInstance> ParseCommandInstance( + const base::DictionaryValue& command, + Command::Origin command_origin, + UserRole role, + std::string* id, + ErrorPtr* error) { + return std::unique_ptr<CommandInstance>{MockParseCommandInstance( + command, command_origin, role, id, error)}; + } StateSnapshot GetAndClearRecordedStateChanges() override { return std::move(MockGetAndClearRecordedStateChanges()); }
diff --git a/src/privet/cloud_delegate.cc b/src/privet/cloud_delegate.cc index 139bfc6..9c8a619 100644 --- a/src/privet/cloud_delegate.cc +++ b/src/privet/cloud_delegate.cc
@@ -15,11 +15,10 @@ #include <weave/provider/task_runner.h> #include "src/backoff_entry.h" -#include "src/commands/command_manager.h" +#include "src/component_manager.h" #include "src/config.h" #include "src/device_registration_info.h" #include "src/privet/constants.h" -#include "src/states/state_manager.h" namespace weave { namespace privet { @@ -42,26 +41,28 @@ public: CloudDelegateImpl(provider::TaskRunner* task_runner, DeviceRegistrationInfo* device, - CommandManager* command_manager, - StateManager* state_manager) + ComponentManager* component_manager) : task_runner_{task_runner}, device_{device}, - command_manager_{command_manager}, - state_manager_{state_manager} { + component_manager_{component_manager} { device_->GetMutableConfig()->AddOnChangedCallback(base::Bind( &CloudDelegateImpl::OnConfigChanged, weak_factory_.GetWeakPtr())); device_->AddGcdStateChangedCallback(base::Bind( &CloudDelegateImpl::OnRegistrationChanged, weak_factory_.GetWeakPtr())); - command_manager_->AddCommandDefChanged(base::Bind( - &CloudDelegateImpl::OnCommandDefChanged, weak_factory_.GetWeakPtr())); - command_manager_->AddCommandAddedCallback(base::Bind( + component_manager_->AddTraitDefChangedCallback(base::Bind( + &CloudDelegateImpl::NotifyOnTraitDefsChanged, + weak_factory_.GetWeakPtr())); + component_manager_->AddCommandAddedCallback(base::Bind( &CloudDelegateImpl::OnCommandAdded, weak_factory_.GetWeakPtr())); - command_manager_->AddCommandRemovedCallback(base::Bind( + component_manager_->AddCommandRemovedCallback(base::Bind( &CloudDelegateImpl::OnCommandRemoved, weak_factory_.GetWeakPtr())); - - state_manager_->AddChangedCallback(base::Bind( - &CloudDelegateImpl::OnStateChanged, weak_factory_.GetWeakPtr())); + component_manager_->AddStateChangedCallback(base::Bind( + &CloudDelegateImpl::NotifyOnComponentTreeChanged, + weak_factory_.GetWeakPtr())); + component_manager_->AddComponentTreeChangedCallback(base::Bind( + &CloudDelegateImpl::NotifyOnComponentTreeChanged, + weak_factory_.GetWeakPtr())); } ~CloudDelegateImpl() override = default; @@ -139,10 +140,20 @@ : ""; } - const base::DictionaryValue& GetState() const override { return state_; } + const base::DictionaryValue& GetLegacyCommandDef() const override { + return component_manager_->GetLegacyCommandDefinitions(); + } - const base::DictionaryValue& GetCommandDef() const override { - return command_defs_; + const base::DictionaryValue& GetLegacyState() const override { + return component_manager_->GetLegacyState(); + } + + const base::DictionaryValue& GetComponents() const override { + return component_manager_->GetComponents(); + } + + const base::DictionaryValue& GetTraits() const override { + return component_manager_->GetTraits(); } void AddCommand(const base::DictionaryValue& command, @@ -162,11 +173,13 @@ } std::string id; - if (!command_manager_->AddCommand(command, role, &id, &error)) + auto command_instance = component_manager_->ParseCommandInstance( + command, Command::Origin::kLocal, role, &id, &error); + if (!command_instance) return callback.Run({}, std::move(error)); - + component_manager_->AddCommand(std::move(command_instance)); command_owners_[id] = user_info.user_id(); - callback.Run(*command_manager_->FindCommand(id)->ToJson(), nullptr); + callback.Run(*component_manager_->FindCommand(id)->ToJson(), nullptr); } void GetCommand(const std::string& id, @@ -200,7 +213,7 @@ for (const auto& it : command_owners_) { if (CanAccessCommand(it.second, user_info, nullptr)) { list_value.Append( - command_manager_->FindCommand(it.first)->ToJson().release()); + component_manager_->FindCommand(it.first)->ToJson().release()); } } @@ -241,21 +254,6 @@ NotifyOnDeviceInfoChanged(); } - void OnStateChanged() { - state_.Clear(); - const auto& state = state_manager_->GetState(); - state_.MergeDictionary(&state); - NotifyOnStateChanged(); - } - - void OnCommandDefChanged() { - command_defs_.Clear(); - const base::DictionaryValue& commands = - command_manager_->GetCommandDictionary().GetCommandsAsJson(); - command_defs_.MergeDictionary(&commands); - NotifyOnCommandDefsChanged(); - } - void OnRegisterSuccess(const std::string& cloud_id) { VLOG(1) << "Device registered: " << cloud_id; setup_state_ = SetupState(SetupState::kSuccess); @@ -304,7 +302,7 @@ return nullptr; } - auto command = command_manager_->FindCommand(command_id); + auto command = component_manager_->FindCommand(command_id); if (!command) return ReturnNotFound(command_id, error); @@ -329,8 +327,7 @@ provider::TaskRunner* task_runner_{nullptr}; DeviceRegistrationInfo* device_{nullptr}; - CommandManager* command_manager_{nullptr}; - StateManager* state_manager_{nullptr}; + ComponentManager* component_manager_{nullptr}; // Primary state of GCD. ConnectionState connection_state_{ConnectionState::kDisabled}; @@ -338,12 +335,6 @@ // State of the current or last setup. SetupState setup_state_{SetupState::kNone}; - // Current device state. - base::DictionaryValue state_; - - // Current commands definitions. - base::DictionaryValue command_defs_; - // Map of command IDs to user IDs. std::map<std::string, uint64_t> command_owners_; @@ -367,22 +358,21 @@ std::unique_ptr<CloudDelegate> CloudDelegate::CreateDefault( provider::TaskRunner* task_runner, DeviceRegistrationInfo* device, - CommandManager* command_manager, - StateManager* state_manager) { + ComponentManager* component_manager) { return std::unique_ptr<CloudDelegateImpl>{new CloudDelegateImpl{ - task_runner, device, command_manager, state_manager}}; + task_runner, device, component_manager}}; } void CloudDelegate::NotifyOnDeviceInfoChanged() { FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceInfoChanged()); } -void CloudDelegate::NotifyOnCommandDefsChanged() { - FOR_EACH_OBSERVER(Observer, observer_list_, OnCommandDefsChanged()); +void CloudDelegate::NotifyOnTraitDefsChanged() { + FOR_EACH_OBSERVER(Observer, observer_list_, OnTraitDefsChanged()); } -void CloudDelegate::NotifyOnStateChanged() { - FOR_EACH_OBSERVER(Observer, observer_list_, OnStateChanged()); +void CloudDelegate::NotifyOnComponentTreeChanged() { + FOR_EACH_OBSERVER(Observer, observer_list_, OnComponentTreeChanged()); } } // namespace privet
diff --git a/src/privet/cloud_delegate.h b/src/privet/cloud_delegate.h index 6396519..e1b1887 100644 --- a/src/privet/cloud_delegate.h +++ b/src/privet/cloud_delegate.h
@@ -22,9 +22,8 @@ namespace weave { -class CommandManager; +class ComponentManager; class DeviceRegistrationInfo; -class StateManager; namespace provider { class TaskRunner; @@ -48,8 +47,8 @@ virtual ~Observer() {} virtual void OnDeviceInfoChanged() {} - virtual void OnCommandDefsChanged() {} - virtual void OnStateChanged() {} + virtual void OnTraitDefsChanged() {} + virtual void OnComponentTreeChanged() {} }; // Returns the ID of the device. @@ -95,11 +94,17 @@ // Returns cloud id if the registered device or empty string if unregistered. virtual std::string GetCloudId() const = 0; - // Returns dictionary with device state. - virtual const base::DictionaryValue& GetState() const = 0; + // Returns dictionary with device state (for legacy APIs). + virtual const base::DictionaryValue& GetLegacyState() const = 0; - // Returns dictionary with commands definitions. - virtual const base::DictionaryValue& GetCommandDef() const = 0; + // Returns dictionary with commands definitions (for legacy APIs). + virtual const base::DictionaryValue& GetLegacyCommandDef() const = 0; + + // Returns dictionary with component tree. + virtual const base::DictionaryValue& GetComponents() const = 0; + + // Returns dictionary with trait definitions. + virtual const base::DictionaryValue& GetTraits() const = 0; // Adds command created from the given JSON representation. virtual void AddCommand(const base::DictionaryValue& command, @@ -126,15 +131,14 @@ } void NotifyOnDeviceInfoChanged(); - void NotifyOnCommandDefsChanged(); - void NotifyOnStateChanged(); + void NotifyOnTraitDefsChanged(); + void NotifyOnComponentTreeChanged(); // Create default instance. static std::unique_ptr<CloudDelegate> CreateDefault( provider::TaskRunner* task_runner, DeviceRegistrationInfo* device, - CommandManager* command_manager, - StateManager* state_manager); + ComponentManager* component_manager); private: base::ObserverList<Observer> observer_list_;
diff --git a/src/privet/mock_delegates.h b/src/privet/mock_delegates.h index 2186f2f..0cfa4ed 100644 --- a/src/privet/mock_delegates.h +++ b/src/privet/mock_delegates.h
@@ -153,8 +153,10 @@ MOCK_CONST_METHOD0(GetSetupState, const SetupState&()); MOCK_METHOD3(Setup, bool(const std::string&, const std::string&, ErrorPtr*)); MOCK_CONST_METHOD0(GetCloudId, std::string()); - MOCK_CONST_METHOD0(GetState, const base::DictionaryValue&()); - MOCK_CONST_METHOD0(GetCommandDef, const base::DictionaryValue&()); + MOCK_CONST_METHOD0(GetLegacyState, const base::DictionaryValue&()); + MOCK_CONST_METHOD0(GetLegacyCommandDef, const base::DictionaryValue&()); + MOCK_CONST_METHOD0(GetComponents, const base::DictionaryValue&()); + MOCK_CONST_METHOD0(GetTraits, const base::DictionaryValue&()); MOCK_METHOD3(AddCommand, void(const base::DictionaryValue&, const UserInfo&, @@ -185,8 +187,11 @@ EXPECT_CALL(*this, GetSetupState()).WillRepeatedly(ReturnRef(setup_state_)); EXPECT_CALL(*this, GetCloudId()).WillRepeatedly(Return("TestCloudId")); test_dict_.Set("test", new base::DictionaryValue); - EXPECT_CALL(*this, GetCommandDef()).WillRepeatedly(ReturnRef(test_dict_)); - EXPECT_CALL(*this, GetState()).WillRepeatedly(ReturnRef(test_dict_)); + EXPECT_CALL(*this, GetLegacyState()).WillRepeatedly(ReturnRef(test_dict_)); + EXPECT_CALL(*this, + GetLegacyCommandDef()).WillRepeatedly(ReturnRef(test_dict_)); + EXPECT_CALL(*this, GetTraits()).WillRepeatedly(ReturnRef(test_dict_)); + EXPECT_CALL(*this, GetComponents()).WillRepeatedly(ReturnRef(test_dict_)); } ConnectionState connection_state_{ConnectionState::kOnline};
diff --git a/src/privet/privet_handler.cc b/src/privet/privet_handler.cc index 157feed..d609787 100644 --- a/src/privet/privet_handler.cc +++ b/src/privet/privet_handler.cc
@@ -378,7 +378,7 @@ ReplyToUpdateRequest(req.callback); } -void PrivetHandler::OnCommandDefsChanged() { +void PrivetHandler::OnTraitDefsChanged() { ++command_defs_fingerprint_; auto pred = [this](const UpdateRequestParameters& params) { return params.command_defs_fingerprint < 0; @@ -390,7 +390,7 @@ update_requests_.erase(last, update_requests_.end()); } -void PrivetHandler::OnStateChanged() { +void PrivetHandler::OnComponentTreeChanged() { ++state_fingerprint_; auto pred = [this](const UpdateRequestParameters& params) { return params.state_fingerprint < 0; @@ -762,8 +762,7 @@ const UserInfo& user_info, const RequestCallback& callback) { base::DictionaryValue output; - base::DictionaryValue* defs = cloud_->GetState().DeepCopy(); - output.Set(kStateKey, defs); + output.Set(kStateKey, cloud_->GetLegacyState().DeepCopy()); output.SetString(kFingerprintKey, std::to_string(state_fingerprint_)); callback.Run(http::kOk, output); @@ -773,8 +772,7 @@ const UserInfo& user_info, const RequestCallback& callback) { base::DictionaryValue output; - base::DictionaryValue* defs = cloud_->GetCommandDef().DeepCopy(); - output.Set(kCommandsKey, defs); + output.Set(kCommandsKey, cloud_->GetLegacyCommandDef().DeepCopy()); output.SetString(kFingerprintKey, std::to_string(command_defs_fingerprint_)); callback.Run(http::kOk, output);
diff --git a/src/privet/privet_handler.h b/src/privet/privet_handler.h index fb9cc94..cc80d43 100644 --- a/src/privet/privet_handler.h +++ b/src/privet/privet_handler.h
@@ -45,8 +45,8 @@ WifiDelegate* wifi); ~PrivetHandler() override; - void OnCommandDefsChanged() override; - void OnStateChanged() override; + void OnTraitDefsChanged() override; + void OnComponentTreeChanged() override; std::vector<std::string> GetHttpPaths() const; std::vector<std::string> GetHttpsPaths() const;
diff --git a/src/privet/privet_handler_unittest.cc b/src/privet/privet_handler_unittest.cc index c212e54..517dda8 100644 --- a/src/privet/privet_handler_unittest.cc +++ b/src/privet/privet_handler_unittest.cc
@@ -630,7 +630,7 @@ EXPECT_PRED2(IsEqualJson, "{'state': {'test': {}}, 'fingerprint': '0'}", HandleRequest("/privet/v3/state", "{}")); - cloud_.NotifyOnStateChanged(); + cloud_.NotifyOnComponentTreeChanged(); EXPECT_PRED2(IsEqualJson, "{'state': {'test': {}}, 'fingerprint': '1'}", HandleRequest("/privet/v3/state", "{}")); @@ -640,7 +640,7 @@ EXPECT_PRED2(IsEqualJson, "{'commands': {'test':{}}, 'fingerprint': '0'}", HandleRequest("/privet/v3/commandDefs", "{}")); - cloud_.NotifyOnCommandDefsChanged(); + cloud_.NotifyOnTraitDefsChanged(); EXPECT_PRED2(IsEqualJson, "{'commands': {'test':{}}, 'fingerprint': '1'}", HandleRequest("/privet/v3/commandDefs", "{}")); @@ -735,8 +735,8 @@ TEST_F(PrivetHandlerSetupTest, CheckForUpdates_NoInput) { EXPECT_CALL(device_, GetHttpRequestTimeout()) .WillOnce(Return(base::TimeDelta::Max())); - cloud_.NotifyOnCommandDefsChanged(); - cloud_.NotifyOnStateChanged(); + cloud_.NotifyOnTraitDefsChanged(); + cloud_.NotifyOnComponentTreeChanged(); const char kInput[] = "{}"; const char kExpected[] = R"({ 'commandsFingerprint': '1', @@ -750,8 +750,8 @@ TEST_F(PrivetHandlerSetupTest, CheckForUpdates_AlreadyChanged) { EXPECT_CALL(device_, GetHttpRequestTimeout()) .WillOnce(Return(base::TimeDelta::Max())); - cloud_.NotifyOnCommandDefsChanged(); - cloud_.NotifyOnStateChanged(); + cloud_.NotifyOnTraitDefsChanged(); + cloud_.NotifyOnComponentTreeChanged(); const char kInput[] = R"({ 'commandsFingerprint': '0', 'stateFingerprint': '0' @@ -775,7 +775,7 @@ EXPECT_PRED2(IsEqualJson, "{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); EXPECT_EQ(0, GetResponseCount()); - cloud_.NotifyOnCommandDefsChanged(); + cloud_.NotifyOnTraitDefsChanged(); EXPECT_EQ(1, GetResponseCount()); const char kExpected[] = R"({ 'commandsFingerprint': '1', @@ -794,7 +794,7 @@ EXPECT_PRED2(IsEqualJson, "{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); EXPECT_EQ(0, GetResponseCount()); - cloud_.NotifyOnStateChanged(); + cloud_.NotifyOnComponentTreeChanged(); EXPECT_EQ(1, GetResponseCount()); const char kExpected[] = R"({ 'commandsFingerprint': '0', @@ -812,9 +812,9 @@ EXPECT_PRED2(IsEqualJson, "{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); EXPECT_EQ(0, GetResponseCount()); - cloud_.NotifyOnCommandDefsChanged(); + cloud_.NotifyOnTraitDefsChanged(); EXPECT_EQ(0, GetResponseCount()); - cloud_.NotifyOnStateChanged(); + cloud_.NotifyOnComponentTreeChanged(); EXPECT_EQ(1, GetResponseCount()); const char kExpected[] = R"({ 'commandsFingerprint': '1', @@ -832,9 +832,9 @@ EXPECT_PRED2(IsEqualJson, "{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); EXPECT_EQ(0, GetResponseCount()); - cloud_.NotifyOnStateChanged(); + cloud_.NotifyOnComponentTreeChanged(); EXPECT_EQ(0, GetResponseCount()); - cloud_.NotifyOnCommandDefsChanged(); + cloud_.NotifyOnTraitDefsChanged(); EXPECT_EQ(1, GetResponseCount()); const char kExpected[] = R"({ 'commandsFingerprint': '1', @@ -953,7 +953,7 @@ EXPECT_PRED2(IsEqualJson, "{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); EXPECT_EQ(0, GetResponseCount()); - cloud_.NotifyOnCommandDefsChanged(); + cloud_.NotifyOnTraitDefsChanged(); EXPECT_EQ(1, GetResponseCount()); const char kExpected[] = R"({ 'commandsFingerprint': '1',
diff --git a/src/privet/privet_manager.cc b/src/privet/privet_manager.cc index e3485c0..a308eec 100644 --- a/src/privet/privet_manager.cc +++ b/src/privet/privet_manager.cc
@@ -18,6 +18,7 @@ #include <weave/provider/network.h> #include "src/bind_lambda.h" +#include "src/component_manager.h" #include "src/device_registration_info.h" #include "src/http_constants.h" #include "src/privet/auth_manager.h" @@ -47,15 +48,14 @@ HttpServer* http_server, Wifi* wifi, DeviceRegistrationInfo* device, - CommandManager* command_manager, - StateManager* state_manager) { + ComponentManager* component_manager) { disable_security_ = device->GetSettings().disable_security; device_ = DeviceDelegate::CreateDefault( task_runner_, http_server->GetHttpPort(), http_server->GetHttpsPort(), http_server->GetRequestTimeout()); - cloud_ = CloudDelegate::CreateDefault(task_runner_, device, command_manager, - state_manager); + cloud_ = CloudDelegate::CreateDefault(task_runner_, device, + component_manager); cloud_observer_.Add(cloud_.get()); auth_.reset(new AuthManager(device->GetSettings().secret,
diff --git a/src/privet/privet_manager.h b/src/privet/privet_manager.h index 95dbbb8..1342584 100644 --- a/src/privet/privet_manager.h +++ b/src/privet/privet_manager.h
@@ -27,11 +27,10 @@ namespace weave { -class CommandManager; +class ComponentManager; class DeviceRegistrationInfo; class DnsServiceDiscovery; class Network; -class StateManager; namespace privet { @@ -52,8 +51,7 @@ provider::HttpServer* http_server, provider::Wifi* wifi, DeviceRegistrationInfo* device, - CommandManager* command_manager, - StateManager* state_manager); + ComponentManager* component_manager); std::string GetCurrentlyConnectedSsid() const;
diff --git a/src/states/error_codes.cc b/src/states/error_codes.cc deleted file mode 100644 index 4b12a45..0000000 --- a/src/states/error_codes.cc +++ /dev/null
@@ -1,20 +0,0 @@ -// 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/states/error_codes.h" - -namespace weave { -namespace errors { -namespace state { - -const char kDomain[] = "buffet_state"; - -const char kPackageNameMissing[] = "package_name_missing"; -const char kPropertyNameMissing[] = "property_name_missing"; -const char kPropertyNotDefined[] = "property_not_defined"; -const char kPropertyRedefinition[] = "property_redefinition"; - -} // namespace state -} // namespace errors -} // namespace weave
diff --git a/src/states/error_codes.h b/src/states/error_codes.h deleted file mode 100644 index 676a199..0000000 --- a/src/states/error_codes.h +++ /dev/null
@@ -1,25 +0,0 @@ -// 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. - -#ifndef LIBWEAVE_SRC_STATES_ERROR_CODES_H_ -#define LIBWEAVE_SRC_STATES_ERROR_CODES_H_ - -namespace weave { -namespace errors { -namespace state { - -// Error domain for state definitions. -extern const char kDomain[]; - -// State-specific error codes. -extern const char kPackageNameMissing[]; -extern const char kPropertyNameMissing[]; -extern const char kPropertyNotDefined[]; -extern const char kPropertyRedefinition[]; - -} // namespace state -} // namespace errors -} // namespace weave - -#endif // LIBWEAVE_SRC_STATES_ERROR_CODES_H_
diff --git a/src/states/mock_state_change_queue_interface.h b/src/states/mock_state_change_queue_interface.h deleted file mode 100644 index fc119cd..0000000 --- a/src/states/mock_state_change_queue_interface.h +++ /dev/null
@@ -1,43 +0,0 @@ -// 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. - -#ifndef LIBWEAVE_SRC_STATES_MOCK_STATE_CHANGE_QUEUE_INTERFACE_H_ -#define LIBWEAVE_SRC_STATES_MOCK_STATE_CHANGE_QUEUE_INTERFACE_H_ - -#include <vector> - -#include <gmock/gmock.h> - -#include "src/states/state_change_queue_interface.h" - -namespace weave { - -class MockStateChangeQueueInterface : public StateChangeQueueInterface { - public: - MOCK_CONST_METHOD0(IsEmpty, bool()); - MOCK_METHOD2(NotifyPropertiesUpdated, - bool(base::Time timestamp, - const base::DictionaryValue& changed_properties)); - MOCK_METHOD0(MockGetAndClearRecordedStateChanges, - std::vector<StateChange>&()); - MOCK_CONST_METHOD0(GetLastStateChangeId, UpdateID()); - MOCK_METHOD1(MockAddOnStateUpdatedCallback, - base::CallbackList<void(UpdateID)>::Subscription*( - const base::Callback<void(UpdateID)>&)); - MOCK_METHOD1(NotifyStateUpdatedOnServer, void(UpdateID)); - - private: - std::vector<StateChange> GetAndClearRecordedStateChanges() override { - return std::move(MockGetAndClearRecordedStateChanges()); - } - - Token AddOnStateUpdatedCallback( - const base::Callback<void(UpdateID)>& callback) override { - return Token{MockAddOnStateUpdatedCallback(callback)}; - } -}; - -} // namespace weave - -#endif // LIBWEAVE_SRC_STATES_MOCK_STATE_CHANGE_QUEUE_INTERFACE_H_
diff --git a/src/states/state_change_queue.cc b/src/states/state_change_queue.cc index b6c67cd..effe7f3 100644 --- a/src/states/state_change_queue.cc +++ b/src/states/state_change_queue.cc
@@ -5,7 +5,6 @@ #include "src/states/state_change_queue.h" #include <base/logging.h> -#include <base/values.h> namespace weave { @@ -38,7 +37,6 @@ std::swap(element_old->second, element_new->second); state_changes_.erase(element_old); } - ++last_change_id_; return true; } @@ -52,15 +50,4 @@ return changes; } -StateChangeQueueInterface::Token StateChangeQueue::AddOnStateUpdatedCallback( - const base::Callback<void(UpdateID)>& callback) { - if (state_changes_.empty()) - callback.Run(last_change_id_); - return Token{callbacks_.Add(callback).release()}; -} - -void StateChangeQueue::NotifyStateUpdatedOnServer(UpdateID update_id) { - callbacks_.Notify(update_id); -} - } // namespace weave
diff --git a/src/states/state_change_queue.h b/src/states/state_change_queue.h index 857ec8b..3aef8d5 100644 --- a/src/states/state_change_queue.h +++ b/src/states/state_change_queue.h
@@ -6,29 +6,36 @@ #define LIBWEAVE_SRC_STATES_STATE_CHANGE_QUEUE_H_ #include <map> +#include <memory> #include <vector> #include <base/macros.h> - -#include "src/states/state_change_queue_interface.h" +#include <base/time/time.h> +#include <base/values.h> namespace weave { +// A simple notification record event to track device state changes. +// The |timestamp| records the time of the state change. +// |changed_properties| contains a property set with the new property values +// which were updated at the time the event was recorded. +struct StateChange { + StateChange(base::Time time, + std::unique_ptr<base::DictionaryValue> properties) + : timestamp{time}, changed_properties{std::move(properties)} {} + base::Time timestamp; + std::unique_ptr<base::DictionaryValue> changed_properties; +}; + // An object to record and retrieve device state change notification events. -class StateChangeQueue : public StateChangeQueueInterface { +class StateChangeQueue { public: explicit StateChangeQueue(size_t max_queue_size); - // Overrides from StateChangeQueueInterface. - bool IsEmpty() const override { return state_changes_.empty(); } bool NotifyPropertiesUpdated( base::Time timestamp, - const base::DictionaryValue& changed_properties) override; - std::vector<StateChange> GetAndClearRecordedStateChanges() override; - UpdateID GetLastStateChangeId() const override { return last_change_id_; } - Token AddOnStateUpdatedCallback( - const base::Callback<void(UpdateID)>& callback) override; - void NotifyStateUpdatedOnServer(UpdateID update_id) override; + const base::DictionaryValue& changed_properties); + std::vector<StateChange> GetAndClearRecordedStateChanges(); private: // Maximum queue size. If it is full, the oldest state update records are @@ -38,13 +45,6 @@ // Accumulated list of device state change notifications. std::map<base::Time, std::unique_ptr<base::DictionaryValue>> state_changes_; - // An ID of last state change update. Each NotifyPropertiesUpdated() - // invocation increments this value by 1. - UpdateID last_change_id_{0}; - - // Callback list for state change queue even sinks. - base::CallbackList<void(UpdateID)> callbacks_; - DISALLOW_COPY_AND_ASSIGN(StateChangeQueue); };
diff --git a/src/states/state_change_queue_interface.h b/src/states/state_change_queue_interface.h deleted file mode 100644 index 7ddce8c..0000000 --- a/src/states/state_change_queue_interface.h +++ /dev/null
@@ -1,71 +0,0 @@ -// 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. - -#ifndef LIBWEAVE_SRC_STATES_STATE_CHANGE_QUEUE_INTERFACE_H_ -#define LIBWEAVE_SRC_STATES_STATE_CHANGE_QUEUE_INTERFACE_H_ - -#include <vector> - -#include <base/callback_list.h> -#include <base/time/time.h> - -namespace base { -class DictionaryValue; -} // namespace base - -namespace weave { - -// A simple notification record event to track device state changes. -// The |timestamp| records the time of the state change. -// |changed_properties| contains a property set with the new property values -// which were updated at the time the event was recorded. -struct StateChange { - StateChange(base::Time time, - std::unique_ptr<base::DictionaryValue> properties) - : timestamp{time}, changed_properties{std::move(properties)} {} - base::Time timestamp; - std::unique_ptr<base::DictionaryValue> changed_properties; -}; - -// An abstract interface to StateChangeQueue to record and retrieve state -// change notification events. -class StateChangeQueueInterface { - public: - using UpdateID = uint64_t; - using Token = - std::unique_ptr<base::CallbackList<void(UpdateID)>::Subscription>; - - // Returns true if the state change notification queue is empty. - virtual bool IsEmpty() const = 0; - - // Called by StateManager when device state properties are updated. - virtual bool NotifyPropertiesUpdated( - base::Time timestamp, - const base::DictionaryValue& changed_properties) = 0; - - // Returns the recorded state changes since last time this method was called. - virtual std::vector<StateChange> GetAndClearRecordedStateChanges() = 0; - - // Returns an ID of last state change update. Each NotifyPropertiesUpdated() - // invocation increments this value by 1. - virtual UpdateID GetLastStateChangeId() const = 0; - - // Subscribes for device state update notifications from cloud server. - // The |callback| will be called every time a state patch with given ID is - // successfully received and processed by GCD server. - // Returns a subscription token. As soon as this token is destroyed, the - // respective callback is removed from the callback list. - virtual Token AddOnStateUpdatedCallback( - const base::Callback<void(UpdateID)>& callback) WARN_UNUSED_RESULT = 0; - - virtual void NotifyStateUpdatedOnServer(UpdateID update_id) = 0; - - protected: - // No one should attempt do destroy the queue through the interface. - virtual ~StateChangeQueueInterface() {} -}; - -} // namespace weave - -#endif // LIBWEAVE_SRC_STATES_STATE_CHANGE_QUEUE_INTERFACE_H_
diff --git a/src/states/state_change_queue_unittest.cc b/src/states/state_change_queue_unittest.cc index b639d37..57cf490 100644 --- a/src/states/state_change_queue_unittest.cc +++ b/src/states/state_change_queue_unittest.cc
@@ -23,8 +23,6 @@ }; TEST_F(StateChangeQueueTest, Empty) { - EXPECT_TRUE(queue_->IsEmpty()); - EXPECT_EQ(0u, queue_->GetLastStateChangeId()); EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty()); } @@ -32,14 +30,10 @@ auto timestamp = base::Time::Now(); ASSERT_TRUE(queue_->NotifyPropertiesUpdated( timestamp, *CreateDictionaryValue("{'prop': {'name': 23}}"))); - EXPECT_FALSE(queue_->IsEmpty()); - EXPECT_EQ(1u, queue_->GetLastStateChangeId()); auto changes = queue_->GetAndClearRecordedStateChanges(); - EXPECT_EQ(1u, queue_->GetLastStateChangeId()); ASSERT_EQ(1u, changes.size()); EXPECT_EQ(timestamp, changes.front().timestamp); EXPECT_JSON_EQ("{'prop':{'name': 23}}", *changes.front().changed_properties); - EXPECT_TRUE(queue_->IsEmpty()); EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty()); } @@ -54,15 +48,12 @@ ASSERT_TRUE(queue_->NotifyPropertiesUpdated( timestamp2, *CreateDictionaryValue(state2))); - EXPECT_EQ(2u, queue_->GetLastStateChangeId()); - EXPECT_FALSE(queue_->IsEmpty()); auto changes = queue_->GetAndClearRecordedStateChanges(); ASSERT_EQ(2u, changes.size()); EXPECT_EQ(timestamp1, changes[0].timestamp); EXPECT_JSON_EQ(state1, *changes[0].changed_properties); EXPECT_EQ(timestamp2, changes[1].timestamp); EXPECT_JSON_EQ(state2, *changes[1].changed_properties); - EXPECT_TRUE(queue_->IsEmpty()); EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty()); } @@ -84,7 +75,6 @@ *CreateDictionaryValue("{'prop': {'name1': 4}}"))); auto changes = queue_->GetAndClearRecordedStateChanges(); - EXPECT_EQ(4u, queue_->GetLastStateChangeId()); ASSERT_EQ(2u, changes.size()); const std::string expected1 = "{'prop': {'name1': 3, 'name2': 2}}"; @@ -113,7 +103,6 @@ start_time + time_delta2, *CreateDictionaryValue("{'prop': {'name10': 10, 'name11': 11}}"))); - EXPECT_EQ(3u, queue_->GetLastStateChangeId()); auto changes = queue_->GetAndClearRecordedStateChanges(); ASSERT_EQ(2u, changes.size()); @@ -128,26 +117,4 @@ EXPECT_JSON_EQ(expected2, *changes[1].changed_properties); } -TEST_F(StateChangeQueueTest, ImmediateStateChangeNotification) { - // When queue is empty, registering a new callback will trigger it. - bool called = false; - auto callback = [&called](StateChangeQueueInterface::UpdateID id) { - called = true; - }; - queue_->AddOnStateUpdatedCallback(base::Bind(callback)); - EXPECT_TRUE(called); -} - -TEST_F(StateChangeQueueTest, DelayedStateChangeNotification) { - // When queue is not empty, registering a new callback will not trigger it. - ASSERT_TRUE(queue_->NotifyPropertiesUpdated( - base::Time::Now(), - *CreateDictionaryValue("{'prop': {'name1': 1, 'name3': 2}}"))); - - auto callback = [](StateChangeQueueInterface::UpdateID id) { - FAIL() << "This should not be called"; - }; - queue_->AddOnStateUpdatedCallback(base::Bind(callback)); -} - } // namespace weave
diff --git a/src/states/state_manager.cc b/src/states/state_manager.cc deleted file mode 100644 index 128f7d8..0000000 --- a/src/states/state_manager.cc +++ /dev/null
@@ -1,232 +0,0 @@ -// 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/states/state_manager.h" - -#include <base/logging.h> -#include <base/values.h> - -#include "src/json_error_codes.h" -#include "src/states/error_codes.h" -#include "src/states/state_change_queue_interface.h" -#include "src/string_utils.h" -#include "src/utils.h" - -namespace weave { - -StateManager::StateManager(StateChangeQueueInterface* state_change_queue) - : state_change_queue_(state_change_queue) { - CHECK(state_change_queue_) << "State change queue not specified"; -} - -StateManager::~StateManager() {} - -void StateManager::AddChangedCallback(const base::Closure& callback) { - on_changed_.push_back(callback); - callback.Run(); // Force to read current state. -} - -const base::DictionaryValue& StateManager::GetState() const { - if (!cached_dict_valid_) { - cached_dict_.Clear(); - for (const auto& pair : packages_) { - cached_dict_.SetWithoutPathExpansion( - pair.first, pair.second->GetValuesAsJson().DeepCopy()); - } - cached_dict_valid_ = true; - } - - return cached_dict_; -} - -bool StateManager::SetProperty(const std::string& name, - const base::Value& value, - ErrorPtr* error) { - bool result = SetPropertyValue(name, value, base::Time::Now(), error); - for (const auto& cb : on_changed_) - cb.Run(); - return result; -} - -const base::Value* StateManager::GetProperty(const std::string& name) const { - auto parts = SplitAtFirst(name, ".", true); - const std::string& package_name = parts.first; - const std::string& property_name = parts.second; - if (package_name.empty() || property_name.empty()) - return nullptr; - - const StatePackage* package = FindPackage(package_name); - if (!package) - return nullptr; - - return package->GetPropertyValue(property_name, nullptr); -} - -bool StateManager::SetPropertyValue(const std::string& full_property_name, - const base::Value& value, - const base::Time& timestamp, - ErrorPtr* error) { - auto parts = SplitAtFirst(full_property_name, ".", true); - const std::string& package_name = parts.first; - const std::string& property_name = parts.second; - const bool split = (full_property_name.find(".") != std::string::npos); - - if (full_property_name.empty() || (split && property_name.empty())) { - Error::AddTo(error, FROM_HERE, errors::state::kDomain, - errors::state::kPropertyNameMissing, - "Property name is missing"); - return false; - } - if (!split || package_name.empty()) { - Error::AddTo(error, FROM_HERE, errors::state::kDomain, - errors::state::kPackageNameMissing, - "Package name is missing in the property name"); - return false; - } - StatePackage* package = FindPackage(package_name); - if (package == nullptr) { - Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain, - errors::state::kPropertyNotDefined, - "Unknown state property package '%s'", - package_name.c_str()); - return false; - } - if (!package->SetPropertyValue(property_name, value, error)) - return false; - - cached_dict_valid_ = false; - - base::DictionaryValue prop_set; - prop_set.Set(full_property_name, value.DeepCopy()); - state_change_queue_->NotifyPropertiesUpdated(timestamp, prop_set); - return true; -} - -std::pair<StateChangeQueueInterface::UpdateID, std::vector<StateChange>> -StateManager::GetAndClearRecordedStateChanges() { - return std::make_pair(state_change_queue_->GetLastStateChangeId(), - state_change_queue_->GetAndClearRecordedStateChanges()); -} - -void StateManager::NotifyStateUpdatedOnServer( - StateChangeQueueInterface::UpdateID id) { - state_change_queue_->NotifyStateUpdatedOnServer(id); -} - -bool StateManager::LoadStateDefinition(const base::DictionaryValue& dict, - ErrorPtr* error) { - for (base::DictionaryValue::Iterator iter(dict); !iter.IsAtEnd(); - iter.Advance()) { - std::string package_name = iter.key(); - if (package_name.empty()) { - Error::AddTo(error, FROM_HERE, errors::kErrorDomain, - errors::kInvalidPackageError, "State package name is empty"); - return false; - } - const base::DictionaryValue* package_dict = nullptr; - if (!iter.value().GetAsDictionary(&package_dict)) { - Error::AddToPrintf(error, FROM_HERE, errors::json::kDomain, - errors::json::kObjectExpected, - "State package '%s' must be an object", - package_name.c_str()); - return false; - } - StatePackage* package = FindOrCreatePackage(package_name); - CHECK(package) << "Unable to create state package " << package_name; - if (!package->AddSchemaFromJson(package_dict, error)) - return false; - } - - return true; -} - -bool StateManager::LoadStateDefinitionFromJson(const std::string& json, - ErrorPtr* error) { - std::unique_ptr<const base::DictionaryValue> dict = LoadJsonDict(json, error); - if (!dict) - return false; - if (!LoadStateDefinition(*dict, error)) { - Error::AddToPrintf(error, FROM_HERE, errors::kErrorDomain, - errors::kSchemaError, - "Failed to load state definition: '%s'", json.c_str()); - return false; - } - return true; -} - -bool StateManager::SetProperties(const base::DictionaryValue& dict, - ErrorPtr* error) { - base::Time timestamp = base::Time::Now(); - bool all_success = true; - for (base::DictionaryValue::Iterator iter(dict); !iter.IsAtEnd(); - iter.Advance()) { - if (iter.key().empty()) { - Error::AddTo(error, FROM_HERE, errors::kErrorDomain, - errors::kInvalidPackageError, "State package name is empty"); - all_success = false; - continue; - } - - const base::DictionaryValue* package_dict = nullptr; - if (!iter.value().GetAsDictionary(&package_dict)) { - Error::AddToPrintf(error, FROM_HERE, errors::json::kDomain, - errors::json::kObjectExpected, - "State package '%s' must be an object", - iter.key().c_str()); - all_success = false; - continue; - } - - for (base::DictionaryValue::Iterator it_prop(*package_dict); - !it_prop.IsAtEnd(); it_prop.Advance()) { - if (!SetPropertyValue(iter.key() + "." + it_prop.key(), it_prop.value(), - timestamp, error)) { - all_success = false; - continue; - } - } - } - for (const auto& cb : on_changed_) - cb.Run(); - return all_success; -} - -bool StateManager::SetPropertiesFromJson(const std::string& json, - ErrorPtr* error) { - std::unique_ptr<const base::DictionaryValue> dict = LoadJsonDict(json, error); - if (!dict) - return false; - if (!SetProperties(*dict, error)) { - Error::AddToPrintf(error, FROM_HERE, errors::kErrorDomain, - errors::kSchemaError, "Failed to load defaults: '%s'", - json.c_str()); - return false; - } - return true; -} - -StatePackage* StateManager::FindPackage(const std::string& package_name) { - auto it = packages_.find(package_name); - return (it != packages_.end()) ? it->second.get() : nullptr; -} - -const StatePackage* StateManager::FindPackage( - const std::string& package_name) const { - auto it = packages_.find(package_name); - return (it != packages_.end()) ? it->second.get() : nullptr; -} - -StatePackage* StateManager::FindOrCreatePackage( - const std::string& package_name) { - StatePackage* package = FindPackage(package_name); - if (package == nullptr) { - std::unique_ptr<StatePackage> new_package{new StatePackage(package_name)}; - package = - packages_.insert(std::make_pair(package_name, std::move(new_package))) - .first->second.get(); - } - return package; -} - -} // namespace weave
diff --git a/src/states/state_manager.h b/src/states/state_manager.h deleted file mode 100644 index bd1eb92..0000000 --- a/src/states/state_manager.h +++ /dev/null
@@ -1,95 +0,0 @@ -// 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. - -#ifndef LIBWEAVE_SRC_STATES_STATE_MANAGER_H_ -#define LIBWEAVE_SRC_STATES_STATE_MANAGER_H_ - -#include <map> -#include <memory> -#include <set> -#include <string> -#include <utility> -#include <vector> - -#include <base/callback.h> -#include <base/macros.h> -#include <weave/error.h> - -#include "src/states/state_change_queue_interface.h" -#include "src/states/state_package.h" - -namespace base { -class DictionaryValue; -class Time; -} // namespace base - -namespace weave { - -// StateManager is the class that aggregates the device state fragments -// provided by device daemons and makes the aggregate device state available -// to the GCD cloud server and local clients. -class StateManager final { - public: - explicit StateManager(StateChangeQueueInterface* state_change_queue); - ~StateManager(); - - void AddChangedCallback(const base::Closure& callback); - bool LoadStateDefinition(const base::DictionaryValue& dict, ErrorPtr* error); - bool LoadStateDefinitionFromJson(const std::string& json, ErrorPtr* error); - bool SetProperties(const base::DictionaryValue& dict, ErrorPtr* error); - bool SetPropertiesFromJson(const std::string& json, ErrorPtr* error); - const base::Value* GetProperty(const std::string& name) const; - bool SetProperty(const std::string& name, - const base::Value& value, - ErrorPtr* error); - const base::DictionaryValue& GetState() const; - - // Returns the recorded state changes since last time this method has been - // called. - std::pair<StateChangeQueueInterface::UpdateID, std::vector<StateChange>> - GetAndClearRecordedStateChanges(); - - // Called to notify that the state patch with |id| has been successfully sent - // to the server and processed. - void NotifyStateUpdatedOnServer(StateChangeQueueInterface::UpdateID id); - - StateChangeQueueInterface* GetStateChangeQueue() const { - return state_change_queue_; - } - - const std::map<std::string, std::unique_ptr<StatePackage>>& packages() const { - return packages_; - } - - private: - friend class BaseApiHandlerTest; - friend class StateManagerTest; - - // Updates a single property value. |full_property_name| must be the full - // name of the property to update in format "package.property". - bool SetPropertyValue(const std::string& full_property_name, - const base::Value& value, - const base::Time& timestamp, - ErrorPtr* error); - - // Finds a package by its name. Returns nullptr if not found. - StatePackage* FindPackage(const std::string& package_name); - const StatePackage* FindPackage(const std::string& package_name) const; - // Finds a package by its name. If none exists, one will be created. - StatePackage* FindOrCreatePackage(const std::string& package_name); - - StateChangeQueueInterface* state_change_queue_; // Owned by Manager. - std::map<std::string, std::unique_ptr<StatePackage>> packages_; - - std::vector<base::Closure> on_changed_; - - mutable base::DictionaryValue cached_dict_; - mutable bool cached_dict_valid_ = false; - - DISALLOW_COPY_AND_ASSIGN(StateManager); -}; - -} // namespace weave - -#endif // LIBWEAVE_SRC_STATES_STATE_MANAGER_H_
diff --git a/src/states/state_manager_unittest.cc b/src/states/state_manager_unittest.cc deleted file mode 100644 index 918fb89..0000000 --- a/src/states/state_manager_unittest.cc +++ /dev/null
@@ -1,272 +0,0 @@ -// 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/states/state_manager.h" - -#include <cstdlib> // for abs(). -#include <vector> - -#include <base/bind.h> -#include <base/values.h> -#include <gmock/gmock.h> -#include <gtest/gtest.h> -#include <weave/provider/test/mock_config_store.h> -#include <weave/test/unittest_utils.h> - -#include "src/commands/schema_constants.h" -#include "src/states/error_codes.h" -#include "src/states/mock_state_change_queue_interface.h" - -namespace weave { - -using testing::_; -using testing::Return; -using testing::ReturnRef; -using test::CreateDictionaryValue; - -namespace { - -const char kBaseDefinition[] = R"({ - "base": { - "manufacturer":{"type":"string"}, - "serialNumber":{"type":"string"} - }, - "device": { - "state_property":{"type":"string"} - } -})"; - -std::unique_ptr<base::DictionaryValue> GetTestSchema() { - return CreateDictionaryValue(kBaseDefinition); -} - -const char kBaseDefaults[] = R"({ - "base": { - "manufacturer":"Test Factory", - "serialNumber":"Test Model" - } -})"; - -std::unique_ptr<base::DictionaryValue> GetTestValues() { - return CreateDictionaryValue(kBaseDefaults); -} - -MATCHER_P(IsState, str, "") { - return arg.Equals(CreateDictionaryValue(str).get()); -} - -} // anonymous namespace - -class StateManagerTest : public ::testing::Test { - public: - void SetUp() override { - // Initial expectations. - EXPECT_CALL(mock_state_change_queue_, IsEmpty()).Times(0); - EXPECT_CALL(mock_state_change_queue_, NotifyPropertiesUpdated(_, _)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(mock_state_change_queue_, MockGetAndClearRecordedStateChanges()) - .Times(0); - mgr_.reset(new StateManager(&mock_state_change_queue_)); - - EXPECT_CALL(*this, OnStateChanged()).Times(2); - mgr_->AddChangedCallback( - base::Bind(&StateManagerTest::OnStateChanged, base::Unretained(this))); - - LoadStateDefinition(GetTestSchema().get(), nullptr); - ASSERT_TRUE(mgr_->SetProperties(*GetTestValues().get(), nullptr)); - } - void TearDown() override { mgr_.reset(); } - - void LoadStateDefinition(const base::DictionaryValue* json, - ErrorPtr* error) { - ASSERT_TRUE(mgr_->LoadStateDefinition(*json, error)); - } - - bool SetPropertyValue(const std::string& name, - const base::Value& value, - ErrorPtr* error) { - return mgr_->SetPropertyValue(name, value, timestamp_, error); - } - - MOCK_CONST_METHOD0(OnStateChanged, void()); - - base::Time timestamp_{base::Time::Now()}; - std::unique_ptr<StateManager> mgr_; - testing::StrictMock<MockStateChangeQueueInterface> mock_state_change_queue_; -}; - -TEST(StateManager, Empty) { - testing::StrictMock<MockStateChangeQueueInterface> mock_state_change_queue; - StateManager manager(&mock_state_change_queue); -} - -TEST_F(StateManagerTest, Initialized) { - auto expected = R"({ - 'base': { - 'manufacturer': 'Test Factory', - 'serialNumber': 'Test Model' - }, - 'device': {} - })"; - EXPECT_JSON_EQ(expected, mgr_->GetState()); -} - -TEST_F(StateManagerTest, LoadStateDefinition) { - auto dict = CreateDictionaryValue(R"({ - 'power': { - 'battery_level':'integer' - } - })"); - LoadStateDefinition(dict.get(), nullptr); - - auto expected = R"({ - 'base': { - 'manufacturer': 'Test Factory', - 'serialNumber': 'Test Model' - }, - 'power': {}, - 'device': {} - })"; - EXPECT_JSON_EQ(expected, mgr_->GetState()); -} - -TEST_F(StateManagerTest, Startup) { - StateManager manager(&mock_state_change_queue_); - - auto state_definition = R"({ - "base": { - "firmwareVersion": {"type":"string"}, - "localDiscoveryEnabled": {"type":"boolean"}, - "localAnonymousAccessMaxRole": { - "type": "string", - "enum": ["none", "viewer", "user"] - }, - "localPairingEnabled": {"type":"boolean"} - }, - "power": {"battery_level":{"type":"integer"}} - })"; - ASSERT_TRUE(manager.LoadStateDefinitionFromJson(state_definition, nullptr)); - - auto state_values = R"({ - "base": { - "firmwareVersion": "unknown", - "localDiscoveryEnabled": false, - "localAnonymousAccessMaxRole": "none", - "localPairingEnabled": false - }, - "power": {"battery_level":44} - })"; - ASSERT_TRUE(manager.SetPropertiesFromJson(state_values, nullptr)); - - auto expected = R"({ - 'base': { - 'firmwareVersion': 'unknown', - 'localAnonymousAccessMaxRole': 'none', - 'localDiscoveryEnabled': false, - 'localPairingEnabled': false - }, - 'power': { - 'battery_level': 44 - } - })"; - EXPECT_JSON_EQ(expected, manager.GetState()); -} - -TEST_F(StateManagerTest, SetPropertyValue) { - const std::string state = "{'device': {'state_property': 'Test Value'}}"; - EXPECT_CALL(mock_state_change_queue_, - NotifyPropertiesUpdated(timestamp_, IsState(state))) - .WillOnce(Return(true)); - ASSERT_TRUE(SetPropertyValue("device.state_property", - base::StringValue{"Test Value"}, nullptr)); - auto expected = R"({ - 'base': { - 'manufacturer': 'Test Factory', - 'serialNumber': 'Test Model' - }, - 'device': { - 'state_property': 'Test Value' - } - })"; - EXPECT_JSON_EQ(expected, mgr_->GetState()); -} - -TEST_F(StateManagerTest, SetPropertyValue_Error_NoName) { - ErrorPtr error; - ASSERT_FALSE(SetPropertyValue("", base::FundamentalValue{0}, &error)); - EXPECT_EQ(errors::state::kDomain, error->GetDomain()); - EXPECT_EQ(errors::state::kPropertyNameMissing, error->GetCode()); -} - -TEST_F(StateManagerTest, SetPropertyValue_Error_NoPackage) { - ErrorPtr error; - ASSERT_FALSE( - SetPropertyValue("state_property", base::FundamentalValue{0}, &error)); - EXPECT_EQ(errors::state::kDomain, error->GetDomain()); - EXPECT_EQ(errors::state::kPackageNameMissing, error->GetCode()); -} - -TEST_F(StateManagerTest, SetPropertyValue_Error_UnknownPackage) { - ErrorPtr error; - ASSERT_FALSE( - SetPropertyValue("power.level", base::FundamentalValue{0}, &error)); - EXPECT_EQ(errors::state::kDomain, error->GetDomain()); - EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode()); -} - -TEST_F(StateManagerTest, SetPropertyValue_Error_UnknownProperty) { - ASSERT_TRUE( - SetPropertyValue("base.level", base::FundamentalValue{0}, nullptr)); -} - -TEST_F(StateManagerTest, GetAndClearRecordedStateChanges) { - EXPECT_CALL(mock_state_change_queue_, - NotifyPropertiesUpdated(timestamp_, _)) - .WillOnce(Return(true)); - ASSERT_TRUE(SetPropertyValue("device.state_property", - base::StringValue{"Test Value"}, nullptr)); - std::vector<StateChange> expected_state; - const std::string expected_val = - "{'device': {'state_property': 'Test Value'}}"; - expected_state.emplace_back( - timestamp_, - CreateDictionaryValue(expected_val)); - EXPECT_CALL(mock_state_change_queue_, MockGetAndClearRecordedStateChanges()) - .WillOnce(ReturnRef(expected_state)); - EXPECT_CALL(mock_state_change_queue_, GetLastStateChangeId()) - .WillOnce(Return(0)); - auto changes = mgr_->GetAndClearRecordedStateChanges(); - ASSERT_EQ(1u, changes.second.size()); - EXPECT_EQ(timestamp_, changes.second.back().timestamp); - EXPECT_JSON_EQ(expected_val, *changes.second.back().changed_properties); -} - -TEST_F(StateManagerTest, SetProperties) { - const std::string state = "{'base': {'manufacturer': 'No Name'}}"; - EXPECT_CALL(mock_state_change_queue_, - NotifyPropertiesUpdated(_, IsState(state))) - .WillOnce(Return(true)); - - EXPECT_CALL(*this, OnStateChanged()).Times(1); - ASSERT_TRUE(mgr_->SetProperties( - *CreateDictionaryValue("{'base':{'manufacturer':'No Name'}}"), nullptr)); - - auto expected = R"({ - 'base': { - 'manufacturer': 'No Name', - 'serialNumber': 'Test Model' - }, - 'device': {} - })"; - EXPECT_JSON_EQ(expected, mgr_->GetState()); -} - -TEST_F(StateManagerTest, GetProperty) { - EXPECT_JSON_EQ("'Test Model'", *mgr_->GetProperty("base.serialNumber")); - EXPECT_EQ(nullptr, mgr_->GetProperty("device.state_property")); - EXPECT_EQ(nullptr, mgr_->GetProperty("device.unknown")); - EXPECT_EQ(nullptr, mgr_->GetProperty("unknown.state_property")); -} - -} // namespace weave
diff --git a/src/states/state_package.cc b/src/states/state_package.cc deleted file mode 100644 index b0ea199..0000000 --- a/src/states/state_package.cc +++ /dev/null
@@ -1,68 +0,0 @@ -// 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/states/state_package.h" - -#include <base/logging.h> -#include <base/values.h> - -#include "src/states/error_codes.h" - -namespace weave { - -StatePackage::StatePackage(const std::string& name) : name_(name) {} - -bool StatePackage::AddSchemaFromJson(const base::DictionaryValue* json, - ErrorPtr* error) { - // Scan first to make sure we have no property redefinitions. - for (base::DictionaryValue::Iterator it(*json); !it.IsAtEnd(); it.Advance()) { - if (types_.HasKey(it.key())) { - Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain, - errors::state::kPropertyRedefinition, - "State property '%s.%s' is already defined", - name_.c_str(), it.key().c_str()); - return false; - } - } - - types_.MergeDictionary(json); - return true; -} - -bool StatePackage::AddValuesFromJson(const base::DictionaryValue* json, - ErrorPtr* error) { - for (base::DictionaryValue::Iterator it(*json); !it.IsAtEnd(); it.Advance()) { - if (!SetPropertyValue(it.key(), it.value(), error)) - return false; - } - return true; -} - -const base::DictionaryValue& StatePackage::GetValuesAsJson() const { - return values_; -} - -const base::Value* StatePackage::GetPropertyValue( - const std::string& property_name, - ErrorPtr* error) const { - const base::Value* value = nullptr; - if (!values_.Get(property_name, &value)) { - Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain, - errors::state::kPropertyNotDefined, - "State property '%s.%s' is not defined", name_.c_str(), - property_name.c_str()); - return nullptr; - } - - return value; -} - -bool StatePackage::SetPropertyValue(const std::string& property_name, - const base::Value& value, - ErrorPtr* error) { - values_.Set(property_name, value.DeepCopy()); - return true; -} - -} // namespace weave
diff --git a/src/states/state_package.h b/src/states/state_package.h deleted file mode 100644 index afc4c52..0000000 --- a/src/states/state_package.h +++ /dev/null
@@ -1,74 +0,0 @@ -// 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. - -#ifndef LIBWEAVE_SRC_STATES_STATE_PACKAGE_H_ -#define LIBWEAVE_SRC_STATES_STATE_PACKAGE_H_ - -#include <map> -#include <memory> -#include <string> - -#include <base/macros.h> -#include <base/values.h> -#include <weave/error.h> - -namespace weave { - -// A package is a set of related state properties. GCD specification defines -// a number of standard state properties in "base" package such as -// "base.manufacturer", "base.model", "base.firmwareVersion" and so on. -class StatePackage final { - public: - explicit StatePackage(const std::string& name); - - // Loads state property definitions from a JSON object and adds them - // to the current package. - bool AddSchemaFromJson(const base::DictionaryValue* json, ErrorPtr* error); - // Loads a set of state property values from a JSON object and assigns them - // to existing properties. A property must be defined prior to loading its - // value. We use this when we load default values during buffet startup. - bool AddValuesFromJson(const base::DictionaryValue* json, ErrorPtr* error); - - // Returns a set of state properties and their values as a JSON object. - // After being aggregated across multiple packages, this becomes the device - // state object passed to the GCD server or a local client in the format - // described by GCD specification, e.g.: - // { - // "base": { - // "manufacturer":"...", - // "model":"..." - // }, - // "printer": { - // "message": "Printer low on cyan ink" - // } - // } - const base::DictionaryValue& GetValuesAsJson() const; - - // Gets the value for a specific state property. |property_name| must not - // include the package name as part of the property name. - const base::Value* GetPropertyValue(const std::string& property_name, - ErrorPtr* error) const; - // Sets the value for a specific state property. |property_name| must not - // include the package name as part of the property name. - bool SetPropertyValue(const std::string& property_name, - const base::Value& value, - ErrorPtr* error); - - // Returns the name of the this package. - const std::string& GetName() const { return name_; } - - const base::DictionaryValue& types() const { return types_; } - - private: - std::string name_; - base::DictionaryValue types_; - base::DictionaryValue values_; - - friend class StatePackageTestHelper; - DISALLOW_COPY_AND_ASSIGN(StatePackage); -}; - -} // namespace weave - -#endif // LIBWEAVE_SRC_STATES_STATE_PACKAGE_H_
diff --git a/src/states/state_package_unittest.cc b/src/states/state_package_unittest.cc deleted file mode 100644 index d09625a..0000000 --- a/src/states/state_package_unittest.cc +++ /dev/null
@@ -1,289 +0,0 @@ -// 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/states/state_package.h" - -#include <memory> -#include <string> - -#include <base/values.h> -#include <gtest/gtest.h> -#include <weave/test/unittest_utils.h> - -#include "src/commands/schema_constants.h" -#include "src/states/error_codes.h" - -namespace weave { - -using test::CreateDictionaryValue; - -class StatePackageTestHelper { - public: - // Returns the state property definitions (types/constraints/etc). - static const base::DictionaryValue& GetTypes(const StatePackage& package) { - return package.types_; - } - // Returns the all state property values in this package. - static const base::DictionaryValue& GetValues(const StatePackage& package) { - return package.values_; - } -}; - -namespace { -std::unique_ptr<base::DictionaryValue> GetTestSchema() { - return CreateDictionaryValue(R"({ - 'color': { - 'type': 'string' - }, - 'direction': { - 'additionalProperties': false, - 'properties': { - 'altitude': { - 'maximum': 90.0, - 'type': 'number' - }, - 'azimuth': { - 'type': 'number' - } - }, - 'type': 'object', - 'required': [ 'azimuth' ] - }, - 'iso': { - 'enum': [50, 100, 200, 400], - 'type': 'integer' - }, - 'light': { - 'type': 'boolean' - } - })"); -} - -std::unique_ptr<base::DictionaryValue> GetTestValues() { - return CreateDictionaryValue(R"({ - 'light': true, - 'color': 'white', - 'direction': {'azimuth':57.2957795, 'altitude':89.9}, - 'iso': 200 - })"); -} - -inline const base::DictionaryValue& GetTypes(const StatePackage& package) { - return StatePackageTestHelper::GetTypes(package); -} -// Returns the all state property values in this package. -inline const base::DictionaryValue& GetValues(const StatePackage& package) { - return StatePackageTestHelper::GetValues(package); -} - -} // anonymous namespace - -class StatePackageTest : public ::testing::Test { - public: - void SetUp() override { - package_.reset(new StatePackage("test")); - ASSERT_TRUE(package_->AddSchemaFromJson(GetTestSchema().get(), nullptr)); - ASSERT_TRUE(package_->AddValuesFromJson(GetTestValues().get(), nullptr)); - } - void TearDown() override { package_.reset(); } - std::unique_ptr<StatePackage> package_; -}; - -TEST(StatePackage, Empty) { - StatePackage package("test"); - EXPECT_EQ("test", package.GetName()); - EXPECT_TRUE(GetTypes(package).empty()); - EXPECT_TRUE(GetValues(package).empty()); -} - -TEST(StatePackage, AddSchemaFromJson_OnEmpty) { - StatePackage package("test"); - ASSERT_TRUE(package.AddSchemaFromJson(GetTestSchema().get(), nullptr)); - EXPECT_EQ(4u, GetTypes(package).size()); - EXPECT_EQ(0u, GetValues(package).size()); - - auto expected = R"({ - 'color': { - 'type': 'string' - }, - 'direction': { - 'additionalProperties': false, - 'properties': { - 'altitude': { - 'maximum': 90.0, - 'type': 'number' - }, - 'azimuth': { - 'type': 'number' - } - }, - 'type': 'object', - 'required': [ 'azimuth' ] - }, - 'iso': { - 'enum': [50, 100, 200, 400], - 'type': 'integer' - }, - 'light': { - 'type': 'boolean' - } - })"; - EXPECT_JSON_EQ(expected, GetTypes(package)); - - EXPECT_JSON_EQ("{}", package.GetValuesAsJson()); -} - -TEST(StatePackage, AddValuesFromJson_OnEmpty) { - StatePackage package("test"); - ASSERT_TRUE(package.AddSchemaFromJson(GetTestSchema().get(), nullptr)); - ASSERT_TRUE(package.AddValuesFromJson(GetTestValues().get(), nullptr)); - EXPECT_EQ(4u, GetValues(package).size()); - auto expected = R"({ - 'color': 'white', - 'direction': { - 'altitude': 89.9, - 'azimuth': 57.2957795 - }, - 'iso': 200, - 'light': true - })"; - EXPECT_JSON_EQ(expected, package.GetValuesAsJson()); -} - -TEST_F(StatePackageTest, AddSchemaFromJson_AddMore) { - auto dict = CreateDictionaryValue(R"({'brightness':{ - 'enum': ['low', 'medium', 'high'], - 'type': 'string' - }})"); - ASSERT_TRUE(package_->AddSchemaFromJson(dict.get(), nullptr)); - EXPECT_EQ(5u, GetTypes(*package_).size()); - EXPECT_EQ(4u, GetValues(*package_).size()); - auto expected = R"({ - 'brightness': { - 'enum': ['low', 'medium', 'high'], - 'type': 'string' - }, - 'color': { - 'type': 'string' - }, - 'direction': { - 'additionalProperties': false, - 'properties': { - 'altitude': { - 'maximum': 90.0, - 'type': 'number' - }, - 'azimuth': { - 'type': 'number' - } - }, - 'type': 'object', - 'required': [ 'azimuth' ] - }, - 'iso': { - 'enum': [50, 100, 200, 400], - 'type': 'integer' - }, - 'light': { - 'type': 'boolean' - } - })"; - EXPECT_JSON_EQ(expected, GetTypes(*package_)); - - expected = R"({ - 'color': 'white', - 'direction': { - 'altitude': 89.9, - 'azimuth': 57.2957795 - }, - 'iso': 200, - 'light': true - })"; - EXPECT_JSON_EQ(expected, package_->GetValuesAsJson()); -} - -TEST_F(StatePackageTest, AddValuesFromJson_AddMore) { - auto dict = CreateDictionaryValue(R"({'brightness':{ - 'enum': ['low', 'medium', 'high'], - 'type': 'string' - }})"); - ASSERT_TRUE(package_->AddSchemaFromJson(dict.get(), nullptr)); - dict = CreateDictionaryValue("{'brightness':'medium'}"); - ASSERT_TRUE(package_->AddValuesFromJson(dict.get(), nullptr)); - EXPECT_EQ(5u, GetValues(*package_).size()); - auto expected = R"({ - 'brightness': 'medium', - 'color': 'white', - 'direction': { - 'altitude': 89.9, - 'azimuth': 57.2957795 - }, - 'iso': 200, - 'light': true - })"; - EXPECT_JSON_EQ(expected, package_->GetValuesAsJson()); -} - -TEST_F(StatePackageTest, AddSchemaFromJson_Error_Redefined) { - auto dict = CreateDictionaryValue(R"({'color': - {'type':'string', 'enum':['white', 'blue', 'red']}})"); - ErrorPtr error; - EXPECT_FALSE(package_->AddSchemaFromJson(dict.get(), &error)); - EXPECT_EQ(errors::state::kDomain, error->GetDomain()); - EXPECT_EQ(errors::state::kPropertyRedefinition, error->GetCode()); -} - -TEST_F(StatePackageTest, AddValuesFromJson_Error_Undefined) { - auto dict = CreateDictionaryValue("{'brightness':'medium'}"); - EXPECT_TRUE(package_->AddValuesFromJson(dict.get(), nullptr)); -} - -TEST_F(StatePackageTest, GetPropertyValue) { - EXPECT_JSON_EQ("'white'", *package_->GetPropertyValue("color", nullptr)); - EXPECT_JSON_EQ("true", *package_->GetPropertyValue("light", nullptr)); - EXPECT_JSON_EQ("200", *package_->GetPropertyValue("iso", nullptr)); - EXPECT_JSON_EQ("{'altitude': 89.9, 'azimuth': 57.2957795}", - *package_->GetPropertyValue("direction", nullptr)); -} - -TEST_F(StatePackageTest, GetPropertyValue_Unknown) { - ErrorPtr error; - EXPECT_EQ(nullptr, package_->GetPropertyValue("volume", &error)); - EXPECT_EQ(errors::state::kDomain, error->GetDomain()); - EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode()); -} - -TEST_F(StatePackageTest, SetPropertyValue_Simple) { - EXPECT_TRUE( - package_->SetPropertyValue("color", base::StringValue{"blue"}, nullptr)); - EXPECT_JSON_EQ("'blue'", *package_->GetPropertyValue("color", nullptr)); - EXPECT_TRUE(package_->SetPropertyValue("light", base::FundamentalValue{false}, - nullptr)); - bool light = false; - ASSERT_TRUE( - package_->GetPropertyValue("light", nullptr)->GetAsBoolean(&light)); - EXPECT_FALSE(light); - EXPECT_TRUE( - package_->SetPropertyValue("iso", base::FundamentalValue{400}, nullptr)); - EXPECT_JSON_EQ("400", *package_->GetPropertyValue("iso", nullptr)); -} - -TEST_F(StatePackageTest, SetPropertyValue_Object) { - EXPECT_TRUE(package_->SetPropertyValue( - "direction", - *CreateDictionaryValue("{'altitude': 45.0, 'azimuth': 15.0}"), nullptr)); - - auto expected = R"({ - 'color': 'white', - 'direction': { - 'altitude': 45.0, - 'azimuth': 15.0 - }, - 'iso': 200, - 'light': true - })"; - EXPECT_JSON_EQ(expected, package_->GetValuesAsJson()); -} - -} // namespace weave