diff options
author | keybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-25 18:20:08 +0000 |
---|---|---|
committer | keybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-25 18:20:08 +0000 |
commit | 9cc40cb2061532d345a6ec925e97563631132e9e (patch) | |
tree | ecfb2c793420271b3b172f27ecebc66ae966bb65 /dbus | |
parent | dbc220a0550880c4d35d75a57f10663d3e1ed240 (diff) | |
download | chromium_src-9cc40cb2061532d345a6ec925e97563631132e9e.zip chromium_src-9cc40cb2061532d345a6ec925e97563631132e9e.tar.gz chromium_src-9cc40cb2061532d345a6ec925e97563631132e9e.tar.bz2 |
Support D-Bus Object Manager
Object Manager is a new standard D-Bus interface, closely related to
the Properties interface. It is used by BlueZ 5.x thus the need to
implement it now.
The intended use is that Chrome D-Bus Client singletons set up a link
to an object manager in their constructor and register themselves to
handle their particular interface.
BUG=220951
TEST=dbus_unittests
Review URL: https://codereview.chromium.org/12491014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@190440 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'dbus')
-rw-r--r-- | dbus/bus.cc | 40 | ||||
-rw-r--r-- | dbus/bus.h | 39 | ||||
-rw-r--r-- | dbus/dbus.gyp | 5 | ||||
-rw-r--r-- | dbus/mock_bus.h | 2 | ||||
-rw-r--r-- | dbus/mock_object_manager.cc | 18 | ||||
-rw-r--r-- | dbus/mock_object_manager.h | 42 | ||||
-rw-r--r-- | dbus/object_manager.cc | 295 | ||||
-rw-r--r-- | dbus/object_manager.h | 316 | ||||
-rw-r--r-- | dbus/object_manager_unittest.cc | 339 | ||||
-rw-r--r-- | dbus/property_unittest.cc | 2 | ||||
-rw-r--r-- | dbus/test_service.cc | 245 | ||||
-rw-r--r-- | dbus/test_service.h | 21 |
12 files changed, 1310 insertions, 54 deletions
diff --git a/dbus/bus.cc b/dbus/bus.cc index e895e58..cfc833d 100644 --- a/dbus/bus.cc +++ b/dbus/bus.cc @@ -13,6 +13,8 @@ #include "base/threading/thread_restrictions.h" #include "base/time.h" #include "dbus/exported_object.h" +#include "dbus/object_manager.h" +#include "dbus/object_path.h" #include "dbus/object_proxy.h" #include "dbus/scoped_dbus_error.h" @@ -322,6 +324,44 @@ void Bus::UnregisterExportedObjectInternal( exported_object->Unregister(); } +ObjectManager* Bus::GetObjectManager(const std::string& service_name, + const ObjectPath& object_path) { + AssertOnOriginThread(); + + // Check if we already have the requested object manager. + const ObjectManagerTable::key_type key(service_name + object_path.value()); + ObjectManagerTable::iterator iter = object_manager_table_.find(key); + if (iter != object_manager_table_.end()) { + return iter->second; + } + + scoped_refptr<ObjectManager> object_manager = + new ObjectManager(this, service_name, object_path); + object_manager_table_[key] = object_manager; + + return object_manager.get(); +} + +void Bus::RemoveObjectManager(const std::string& service_name, + const ObjectPath& object_path) { + AssertOnOriginThread(); + + const ObjectManagerTable::key_type key(service_name + object_path.value()); + ObjectManagerTable::iterator iter = object_manager_table_.find(key); + if (iter == object_manager_table_.end()) + return; + + scoped_refptr<ObjectManager> object_manager = iter->second; + object_manager_table_.erase(iter); +} + +void Bus::GetManagedObjects() { + for (ObjectManagerTable::iterator iter = object_manager_table_.begin(); + iter != object_manager_table_.end(); ++iter) { + iter->second->GetManagedObjects(); + } +} + bool Bus::Connect() { // dbus_bus_get_private() and dbus_bus_get() are blocking calls. AssertOnDBusThread(); @@ -32,6 +32,7 @@ class Location; namespace dbus { class ExportedObject; +class ObjectManager; class ObjectProxy; // Bus is used to establish a connection with D-Bus, create object @@ -302,6 +303,37 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> { // Must be called in the origin thread. virtual void UnregisterExportedObject(const ObjectPath& object_path); + + // Gets an object manager for the given remote object path |object_path| + // exported by the service |service_name|. + // + // Returns an existing object manager if the bus object already owns a + // matching object manager, never returns NULL. + // + // The caller must not delete the returned object, the bus retains ownership + // of all object managers. + // + // Must be called in the origin thread. + virtual ObjectManager* GetObjectManager(const std::string& service_name, + const ObjectPath& object_path); + + // Unregisters the object manager for the given remote object path + // |object_path| exported by the srevice |service_name|. + // + // Getting an object manager for the same remote object after this call + // will return a new object, method calls on any remaining copies of the + // previous object are not permitted. + // + // Must be called in the origin thread. + virtual void RemoveObjectManager(const std::string& service_name, + const ObjectPath& object_path); + + // Instructs all registered object managers to retrieve their set of managed + // objects from their respective remote objects. There is no need to call this + // manually, this is called automatically by the D-Bus thread manager once + // implementation classes are registered. + virtual void GetManagedObjects(); + // Shuts down the bus and blocks until it's done. More specifically, this // function does the following: // @@ -608,6 +640,13 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> { scoped_refptr<dbus::ExportedObject> > ExportedObjectTable; ExportedObjectTable exported_object_table_; + // ObjectManagerTable is used to hold the object managers created by the + // bus object. Key is a concatenated string of service name + object path, + // like "org.chromium.TestService/org/chromium/TestObject". + typedef std::map<std::string, + scoped_refptr<dbus::ObjectManager> > ObjectManagerTable; + ObjectManagerTable object_manager_table_; + bool async_operations_set_up_; bool shutdown_completed_; diff --git a/dbus/dbus.gyp b/dbus/dbus.gyp index 0ca5a2c..847e7d97 100644 --- a/dbus/dbus.gyp +++ b/dbus/dbus.gyp @@ -33,6 +33,8 @@ 'file_descriptor.h', 'message.cc', 'message.h', + 'object_manager.cc', + 'object_manager.h', 'object_path.cc', 'object_path.h', 'object_proxy.cc', @@ -71,6 +73,8 @@ 'mock_bus.h', 'mock_exported_object.cc', 'mock_exported_object.h', + 'mock_object_manager.cc', + 'mock_object_manager.h', 'mock_object_proxy.cc', 'mock_object_proxy.h', ], @@ -98,6 +102,7 @@ 'end_to_end_sync_unittest.cc', 'message_unittest.cc', 'mock_unittest.cc', + 'object_manager_unittest.cc', 'property_unittest.cc', 'signal_sender_verification_unittest.cc', 'string_util_unittest.cc', diff --git a/dbus/mock_bus.h b/dbus/mock_bus.h index 9a87ecf7..26113d4 100644 --- a/dbus/mock_bus.h +++ b/dbus/mock_bus.h @@ -26,6 +26,8 @@ class MockBus : public Bus { int options)); MOCK_METHOD1(GetExportedObject, ExportedObject*( const ObjectPath& object_path)); + MOCK_METHOD2(GetObjectManager, ObjectManager*(const std::string&, + const ObjectPath&)); MOCK_METHOD0(ShutdownAndBlock, void()); MOCK_METHOD0(ShutdownOnDBusThreadAndBlock, void()); MOCK_METHOD0(Connect, bool()); diff --git a/dbus/mock_object_manager.cc b/dbus/mock_object_manager.cc new file mode 100644 index 0000000..dcba78e --- /dev/null +++ b/dbus/mock_object_manager.cc @@ -0,0 +1,18 @@ +// Copyright (c) 2013 The Chromium 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 "dbus/mock_object_manager.h" + +namespace dbus { + +MockObjectManager::MockObjectManager(Bus* bus, + const std::string& service_name, + const ObjectPath& object_path) + : ObjectManager(bus, service_name, object_path) { +} + +MockObjectManager::~MockObjectManager() { +} + +} // namespace dbus diff --git a/dbus/mock_object_manager.h b/dbus/mock_object_manager.h new file mode 100644 index 0000000..e4c76ba --- /dev/null +++ b/dbus/mock_object_manager.h @@ -0,0 +1,42 @@ +// Copyright (c) 2013 The Chromium 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 DBUS_MOCK_OBJECT_MANAGER_H_ +#define DBUS_MOCK_OBJECT_MANAGER_H_ + +#include <string> + +#include "dbus/message.h" +#include "dbus/object_manager.h" +#include "dbus/object_path.h" +#include "dbus/object_proxy.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace dbus { + +// Mock for ObjectManager. +class MockObjectManager : public ObjectManager { + public: + MockObjectManager(Bus* bus, + const std::string& service_name, + const ObjectPath& object_path); + + MOCK_METHOD2(RegisterInterface, void(const std::string&, + Interface*)); + MOCK_METHOD1(UnregisterInterface, void(const std::string&)); + MOCK_METHOD0(GetObjects, std::vector<ObjectPath>()); + MOCK_METHOD1(GetObjectsWithInterface, + std::vector<ObjectPath>(const std::string&)); + MOCK_METHOD1(GetObjectProxy, ObjectProxy*(const ObjectPath&)); + MOCK_METHOD2(GetProperties, PropertySet*(const ObjectPath&, + const std::string&)); + MOCK_METHOD0(GetManagedObjects, void()); + + protected: + virtual ~MockObjectManager(); +}; + +} // namespace dbus + +#endif // DBUS_MOCK_OBJECT_MANAGER_H_ diff --git a/dbus/object_manager.cc b/dbus/object_manager.cc new file mode 100644 index 0000000..0386228 --- /dev/null +++ b/dbus/object_manager.cc @@ -0,0 +1,295 @@ +// Copyright (c) 2013 The Chromium 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 "dbus/object_manager.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "dbus/bus.h" +#include "dbus/message.h" +#include "dbus/object_proxy.h" +#include "dbus/property.h" + +namespace dbus { + +ObjectManager::Object::Object() + : object_proxy(NULL) { +} + +ObjectManager::Object::~Object() { +} + +ObjectManager::ObjectManager(Bus* bus, + const std::string& service_name, + const ObjectPath& object_path) + : bus_(bus), + service_name_(service_name), + object_path_(object_path), + weak_ptr_factory_(this) { + DVLOG(1) << "Creating ObjectManager for " << service_name_ + << " " << object_path_.value(); + + DCHECK(bus_); + object_proxy_ = bus_->GetObjectProxy(service_name_, object_path_); + + object_proxy_->ConnectToSignal( + kObjectManagerInterface, + kObjectManagerInterfacesAdded, + base::Bind(&ObjectManager::InterfacesAddedReceived, + weak_ptr_factory_.GetWeakPtr()), + base::Bind(&ObjectManager::InterfacesAddedConnected, + weak_ptr_factory_.GetWeakPtr())); + + object_proxy_->ConnectToSignal( + kObjectManagerInterface, + kObjectManagerInterfacesRemoved, + base::Bind(&ObjectManager::InterfacesRemovedReceived, + weak_ptr_factory_.GetWeakPtr()), + base::Bind(&ObjectManager::InterfacesRemovedConnected, + weak_ptr_factory_.GetWeakPtr())); + + GetManagedObjects(); +} + +ObjectManager::~ObjectManager() { + // Clean up Object structures + for (ObjectMap::iterator iter = object_map_.begin(); + iter != object_map_.end(); ++iter) { + Object* object = iter->second; + + for (Object::PropertiesMap::iterator piter = object->properties_map.begin(); + piter != object->properties_map.end(); ++piter) { + PropertySet* properties = piter->second; + delete properties; + } + + delete object; + } +} + +void ObjectManager::RegisterInterface(const std::string& interface_name, + Interface* interface) { + interface_map_[interface_name] = interface; +} + +void ObjectManager::UnregisterInterface(const std::string& interface_name) { + InterfaceMap::iterator iter = interface_map_.find(interface_name); + if (iter != interface_map_.end()) + interface_map_.erase(iter); +} + +std::vector<ObjectPath> ObjectManager::GetObjects() { + std::vector<ObjectPath> object_paths; + + for (ObjectMap::iterator iter = object_map_.begin(); + iter != object_map_.end(); ++iter) + object_paths.push_back(iter->first); + + return object_paths; +} + +std::vector<ObjectPath> ObjectManager::GetObjectsWithInterface( + const std::string& interface_name) { + std::vector<ObjectPath> object_paths; + + for (ObjectMap::iterator oiter = object_map_.begin(); + oiter != object_map_.end(); ++oiter) { + Object* object = oiter->second; + + Object::PropertiesMap::iterator piter = + object->properties_map.find(interface_name); + if (piter != object->properties_map.end()) + object_paths.push_back(oiter->first); + } + + return object_paths; +} + +ObjectProxy* ObjectManager::GetObjectProxy(const ObjectPath& object_path) { + ObjectMap::iterator iter = object_map_.find(object_path); + if (iter == object_map_.end()) + return NULL; + + Object* object = iter->second; + return object->object_proxy; +} + +PropertySet* ObjectManager::GetProperties(const ObjectPath& object_path, + const std::string& interface_name) { + ObjectMap::iterator iter = object_map_.find(object_path); + if (iter == object_map_.end()) + return NULL; + + Object* object = iter->second; + Object::PropertiesMap::iterator piter = + object->properties_map.find(interface_name); + if (piter == object->properties_map.end()) + return NULL; + + return piter->second; +} + +void ObjectManager::GetManagedObjects() { + MethodCall method_call(kObjectManagerInterface, + kObjectManagerGetManagedObjects); + + object_proxy_->CallMethod( + &method_call, + ObjectProxy::TIMEOUT_USE_DEFAULT, + base::Bind(&ObjectManager::OnGetManagedObjects, + weak_ptr_factory_.GetWeakPtr())); +} + +void ObjectManager::OnGetManagedObjects(Response* response) { + if (response != NULL) { + MessageReader reader(response); + MessageReader array_reader(NULL); + if (!reader.PopArray(&array_reader)) + return; + + while (array_reader.HasMoreData()) { + MessageReader dict_entry_reader(NULL); + ObjectPath object_path; + if (!array_reader.PopDictEntry(&dict_entry_reader) || + !dict_entry_reader.PopObjectPath(&object_path)) + continue; + + UpdateObject(object_path, &dict_entry_reader); + } + + } else { + LOG(WARNING) << service_name_ << " " << object_path_.value() + << ": Failed to get managed objects"; + } +} + +void ObjectManager::InterfacesAddedReceived(Signal* signal) { + DCHECK(signal); + MessageReader reader(signal); + ObjectPath object_path; + if (!reader.PopObjectPath(&object_path)) { + LOG(WARNING) << service_name_ << " " << object_path_.value() + << ": InterfacesAdded signal has incorrect parameters: " + << signal->ToString(); + return; + } + + UpdateObject(object_path, &reader); +} + +void ObjectManager::InterfacesAddedConnected(const std::string& interface_name, + const std::string& signal_name, + bool success) { + LOG_IF(WARNING, !success) << service_name_ << " " << object_path_.value() + << ": Failed to connect to InterfacesAdded signal."; +} + +void ObjectManager::InterfacesRemovedReceived(Signal* signal) { + DCHECK(signal); + MessageReader reader(signal); + ObjectPath object_path; + std::vector<std::string> interface_names; + if (!reader.PopObjectPath(&object_path) || + !reader.PopArrayOfStrings(&interface_names)) { + LOG(WARNING) << service_name_ << " " << object_path_.value() + << ": InterfacesRemoved signal has incorrect parameters: " + << signal->ToString(); + return; + } + + for (size_t i = 0; i < interface_names.size(); ++i) + RemoveInterface(object_path, interface_names[i]); +} + +void ObjectManager::InterfacesRemovedConnected( + const std::string& interface_name, + const std::string& signal_name, + bool success) { + LOG_IF(WARNING, !success) << service_name_ << " " << object_path_.value() + << ": Failed to connect to " + << "InterfacesRemoved signal."; +} + +void ObjectManager::UpdateObject(const ObjectPath& object_path, + MessageReader* reader) { + DCHECK(reader); + MessageReader array_reader(NULL); + if (!reader->PopArray(&array_reader)) + return; + + while (array_reader.HasMoreData()) { + MessageReader dict_entry_reader(NULL); + std::string interface_name; + if (!array_reader.PopDictEntry(&dict_entry_reader) || + !dict_entry_reader.PopString(&interface_name)) + continue; + + AddInterface(object_path, interface_name, &dict_entry_reader); + } +} + + +void ObjectManager::AddInterface(const ObjectPath& object_path, + const std::string& interface_name, + MessageReader* reader) { + InterfaceMap::iterator iiter = interface_map_.find(interface_name); + if (iiter == interface_map_.end()) + return; + Interface* interface = iiter->second; + + ObjectMap::iterator oiter = object_map_.find(object_path); + Object* object; + if (oiter == object_map_.end()) { + object = object_map_[object_path] = new Object; + object->object_proxy = bus_->GetObjectProxy(service_name_, object_path); + } else + object = oiter->second; + + Object::PropertiesMap::iterator piter = + object->properties_map.find(interface_name); + PropertySet* property_set; + const bool interface_added = (piter == object->properties_map.end()); + if (interface_added) { + property_set = object->properties_map[interface_name] = + interface->CreateProperties(object->object_proxy, + object_path, interface_name); + property_set->ConnectSignals(); + } else + property_set = piter->second; + + property_set->UpdatePropertiesFromReader(reader); + + if (interface_added) + interface->ObjectAdded(object_path, interface_name); +} + +void ObjectManager::RemoveInterface(const ObjectPath& object_path, + const std::string& interface_name) { + ObjectMap::iterator oiter = object_map_.find(object_path); + if (oiter == object_map_.end()) + return; + Object* object = oiter->second; + + Object::PropertiesMap::iterator piter = + object->properties_map.find(interface_name); + if (piter == object->properties_map.end()) + return; + + // Inform the interface before removing the properties structure or object + // in case it needs details from them to make its own decisions. + InterfaceMap::iterator iiter = interface_map_.find(interface_name); + if (iiter != interface_map_.end()) { + Interface* interface = iiter->second; + interface->ObjectRemoved(object_path, interface_name); + } + + object->properties_map.erase(piter); + + if (object->properties_map.empty()) { + object_map_.erase(oiter); + delete object; + } +} + +} // namespace dbus diff --git a/dbus/object_manager.h b/dbus/object_manager.h new file mode 100644 index 0000000..333f69e --- /dev/null +++ b/dbus/object_manager.h @@ -0,0 +1,316 @@ +// Copyright (c) 2013 The Chromium 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 DBUS_OBJECT_MANAGER_H_ +#define DBUS_OBJECT_MANAGER_H_ + +#include <map> + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "dbus/object_path.h" +#include "dbus/property.h" + +// Newer D-Bus services implement the Object Manager interface to inform other +// clients about the objects they export, the properties of those objects, and +// notification of changes in the set of available objects: +// http://dbus.freedesktop.org/doc/dbus-specification.html +// #standard-interfaces-objectmanager +// +// This interface is very closely tied to the Properties interface, and uses +// even more levels of nested dictionaries and variants. In addition to +// simplifying implementation, since there tends to be a single object manager +// per service, spanning the complete set of objects an interfaces available, +// the classes implemented here make dealing with this interface simpler. +// +// Except where noted, use of this class replaces the need for the code +// documented in dbus/property.h +// +// Client implementation classes should begin by deriving from the +// dbus::ObjectManager::Interface class, and defining a Properties structure as +// documented in dbus/property.h. +// +// Example: +// class ExampleClient : public dbus::ObjectManager::Interface { +// public: +// struct Properties : public dbus::PropertySet { +// dbus::Property<std::string> name; +// dbus::Property<uint16> version; +// dbus::Property<dbus::ObjectPath> parent; +// dbus::Property<std::vector<std::string> > children; +// +// Properties(dbus::ObjectProxy* object_proxy, +// const PropertyChangedCallback callback) +// : dbus::PropertySet(object_proxy, kExampleInterface, callback) { +// RegisterProperty("Name", &name); +// RegisterProperty("Version", &version); +// RegisterProperty("Parent", &parent); +// RegisterProperty("Children", &children); +// } +// virtual ~Properties() {} +// }; +// +// The link between the implementation class and the object manager is set up +// in the constructor and removed in the destructor; the class should maintain +// a pointer to its object manager for use in other methods and establish +// itself as the implementation class for its interface. +// +// Example: +// explicit ExampleClient::ExampleClient(dbus::Bus* bus) +// : bus_(bus), +// weak_ptr_factory_(this) { +// object_manager_ = bus_->GetObjectManager(kServiceName, kManagerPath); +// object_manager_->RegisterInterface(kInterface, this); +// } +// +// virtual ExampleClient::~ExampleClient() { +// object_manager_->UnregisterInterface(kInterface); +// } +// +// The D-Bus thread manager takes care of issuing the necessary call to +// GetManagedObjects() after the implementation classes have been set up. +// +// The object manager interface class has one abstract method that must be +// implemented by the class to create Properties structures on demand. As well +// as implementing this, you will want to implement a public GetProperties() +// method. +// +// Example: +// dbus::PropertySet* CreateProperties(dbus::ObjectProxy* object_proxy, +// const std::string& interface_name) +// OVERRIDE { +// Properties* properties = new Properties( +// object_proxy, interface_name, +// base::Bind(&PropertyChanged, +// weak_ptr_factory_.GetWeakPtr(), +// object_path)); +// return static_cast<dbus::PropertySet*>(properties); +// } +// +// Properties* GetProperties(const dbus::ObjectPath& object_path) { +// return static_cast<Properties*>( +// object_manager_->GetProperties(object_path, kInterface)); +// } +// +// Note that unlike classes that only use dbus/property.h there is no need +// to connect signals or obtain the initial values of properties. The object +// manager class handles that for you. +// +// PropertyChanged is a method of your own to notify your observers of a change +// in your properties, either as a result of a signal from the Properties +// interface or from the Object Manager interface. You may also wish to +// implement the optional ObjectAdded and ObjectRemoved methods of the class +// to likewise notify observers. +// +// When your class needs an object proxy for a given object path, it may +// obtain it from the object manager. Unlike the equivalent method on the bus +// this will return NULL if the object is not known. +// +// object_proxy = object_manager_->GetObjectProxy(object_path); +// if (object_proxy) { +// ... +// } +// +// There is no need for code using your implementation class to be aware of the +// use of object manager behind the scenes, the rules for updating properties +// documented in dbus/property.h still apply. + +namespace dbus { + +const char kObjectManagerInterface[] = "org.freedesktop.DBus.ObjectManager"; +const char kObjectManagerGetManagedObjects[] = "GetManagedObjects"; +const char kObjectManagerInterfacesAdded[] = "InterfacesAdded"; +const char kObjectManagerInterfacesRemoved[] = "InterfacesRemoved"; + +class Bus; +class MessageReader; +class ObjectProxy; +class Response; +class Signal; + +// ObjectManager implements both the D-Bus client components of the D-Bus +// Object Manager interface, as internal methods, and a public API for +// client classes to utilize. +class CHROME_DBUS_EXPORT ObjectManager + : public base::RefCountedThreadSafe<ObjectManager> { +public: + // ObjectManager::Interface must be implemented by any class wishing to have + // its remote objects managed by an ObjectManager. + class Interface { + public: + virtual ~Interface() {} + + // Called by ObjectManager to create a Properties structure for the remote + // D-Bus object identified by |object_path| and accessibile through + // |object_proxy|. The D-Bus interface name |interface_name| is that passed + // to RegisterInterface() by the implementation class. + // + // The implementation class should create and return an instance of its own + // subclass of dbus::PropertySet; ObjectManager will then connect signals + // and update the properties from its own internal message reader. + virtual PropertySet* CreateProperties( + ObjectProxy *object_proxy, + const dbus::ObjectPath& object_path, + const std::string& interface_name) = 0; + + // Called by ObjectManager to inform the implementation class that an + // object has been added with the path |object_path|. The D-Bus interface + // name |interface_name| is that passed to RegisterInterface() by the + // implementation class. + // + // If a new object implements multiple interfaces, this method will be + // called on each interface implementation with differing values of + // |interface_name| as appropriate. An implementation class will only + // receive multiple calls if it has registered for multiple interfaces. + virtual void ObjectAdded(const ObjectPath& object_path, + const std::string& interface_name) { } + + // Called by ObjectManager to inform the implementation class than an + // object with the path |object_path| has been removed. Ths D-Bus interface + // name |interface_name| is that passed to RegisterInterface() by the + // implementation class. Multiple interfaces are handled as with + // ObjectAdded(). + // + // This method will be called before the Properties structure and the + // ObjectProxy object for the given interface are cleaned up, it is safe + // to retrieve them during removal to vary processing. + virtual void ObjectRemoved(const ObjectPath& object_path, + const std::string& interface_name) { } + }; + + // Client code should use Bus::GetObjectManager() instead of this constructor. + ObjectManager(Bus* bus, + const std::string& service_name, + const ObjectPath& object_path); + + // Register a client implementation class |interface| for the given D-Bus + // interface named in |interface_name|. That object's CreateProperties() + // method will be used to create instances of dbus::PropertySet* when + // required. + void RegisterInterface(const std::string& interface_name, + Interface* interface); + + // Unregister the implementation class for the D-Bus interface named in + // |interface_name|, objects and properties of this interface will be + // ignored. + void UnregisterInterface(const std::string& interface_name); + + // Returns a list of object paths, in an undefined order, of objects known + // to this manager. + std::vector<ObjectPath> GetObjects(); + + // Returns the list of object paths, in an undefined order, of objects + // implementing the interface named in |interface_name| known to this manager. + std::vector<ObjectPath> GetObjectsWithInterface( + const std::string& interface_name); + + // Returns a ObjectProxy pointer for the given |object_path|. Unlike + // the equivalent method on Bus this will return NULL if the object + // manager has not been informed of that object's existance. + ObjectProxy* GetObjectProxy(const ObjectPath& object_path); + + // Returns a PropertySet* pointer for the given |object_path| and + // |interface_name|, or NULL if the object manager has not been informed of + // that object's existance or the interface's properties. The caller should + // cast the returned pointer to the appropriate type, e.g.: + // static_cast<Properties*>(GetProperties(object_path, my_interface)); + PropertySet* GetProperties(const ObjectPath& object_path, + const std::string& interface_name); + + // Instructs the object manager to refresh its list of managed objects; + // automatically called by the D-Bus thread manager, there should never be + // a need to call this manually. + void GetManagedObjects(); + + protected: + virtual ~ObjectManager(); + + private: + friend class base::RefCountedThreadSafe<ObjectManager>; + + + // Called by dbus:: in response to the GetManagedObjects() method call. + void OnGetManagedObjects(Response* response); + + // Called by dbus:: when an InterfacesAdded signal is received and initially + // connected. + void InterfacesAddedReceived(Signal* signal); + void InterfacesAddedConnected(const std::string& interface_name, + const std::string& signal_name, + bool success); + + // Called by dbus:: when an InterfacesRemoved signal is received and + // initially connected. + void InterfacesRemovedReceived(Signal* signal); + void InterfacesRemovedConnected(const std::string& interface_name, + const std::string& signal_name, + bool success); + + // Updates the map entry for the object with path |object_path| using the + // D-Bus message in |reader|, which should consist of an dictionary mapping + // interface names to properties dictionaries as recieved by both the + // GetManagedObjects() method return and the InterfacesAdded() signal. + void UpdateObject(const ObjectPath& object_path, MessageReader* reader); + + // Updates the properties structure of the object with path |object_path| + // for the interface named |interface_name| using the D-Bus message in + // |reader| which should consist of the properties dictionary for that + // interface. + // + // Called by UpdateObjects() for each interface in the dictionary; this + // method takes care of both creating the entry in the ObjectMap and + // ObjectProxy if required, as well as the PropertySet instance for that + // interface if necessary. + void AddInterface(const ObjectPath& object_path, + const std::string& interface_name, + MessageReader* reader); + + // Removes the properties structure of the object with path |object_path| + // for the interfaces named |interface_name|. + // + // If no further interfaces remain, the entry in the ObjectMap is discarded. + void RemoveInterface(const ObjectPath& object_path, + const std::string& interface_name); + + Bus* bus_; + std::string service_name_; + ObjectPath object_path_; + ObjectProxy* object_proxy_; + + // Maps the name of an interface to the implementation class used for + // instantiating PropertySet structures for that interface's properties. + typedef std::map<std::string, Interface*> InterfaceMap; + InterfaceMap interface_map_; + + // Each managed object consists of a ObjectProxy used to make calls + // against that object and a collection of D-Bus interface names and their + // associated PropertySet structures. + struct Object { + Object(); + ~Object(); + + ObjectProxy* object_proxy; + + // Maps the name of an interface to the specific PropertySet structure + // of that interface's properties. + typedef std::map<const std::string, PropertySet*> PropertiesMap; + PropertiesMap properties_map; + }; + + // Maps the object path of an object to the Object structure. + typedef std::map<const ObjectPath, Object*> ObjectMap; + ObjectMap object_map_; + + // Weak pointer factory for generating 'this' pointers that might live longer + // than we do. + // Note: This should remain the last member so it'll be destroyed and + // invalidate its weak pointers before any other members are destroyed. + base::WeakPtrFactory<ObjectManager> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(ObjectManager); +}; + +} // namespace dbus + +#endif // DBUS_OBJECT_MANAGER_H_ diff --git a/dbus/object_manager_unittest.cc b/dbus/object_manager_unittest.cc new file mode 100644 index 0000000..9f86bc9 --- /dev/null +++ b/dbus/object_manager_unittest.cc @@ -0,0 +1,339 @@ +// Copyright (c) 2013 The Chromium 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 "dbus/object_manager.h" + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/threading/thread.h" +#include "base/threading/thread_restrictions.h" +#include "dbus/bus.h" +#include "dbus/object_path.h" +#include "dbus/object_proxy.h" +#include "dbus/property.h" +#include "dbus/test_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +// The object manager test exercises the asynchronous APIs in ObjectManager, +// and by extension PropertySet and Property<>. +class ObjectManagerTest + : public testing::Test, + public dbus::ObjectManager::Interface { + public: + ObjectManagerTest() { + } + + struct Properties : public dbus::PropertySet { + dbus::Property<std::string> name; + dbus::Property<int16> version; + dbus::Property<std::vector<std::string> > methods; + dbus::Property<std::vector<dbus::ObjectPath> > objects; + + Properties(dbus::ObjectProxy* object_proxy, + const std::string& interface_name, + PropertyChangedCallback property_changed_callback) + : dbus::PropertySet(object_proxy, interface_name, + property_changed_callback) { + RegisterProperty("Name", &name); + RegisterProperty("Version", &version); + RegisterProperty("Methods", &methods); + RegisterProperty("Objects", &objects); + } + }; + + virtual dbus::PropertySet* CreateProperties( + dbus::ObjectProxy* object_proxy, + const dbus::ObjectPath& object_path, + const std::string& interface_name) OVERRIDE { + Properties* properties = new Properties( + object_proxy, interface_name, + base::Bind(&ObjectManagerTest::OnPropertyChanged, + base::Unretained(this), object_path)); + return static_cast<dbus::PropertySet*>(properties); + } + + virtual void SetUp() { + // Make the main thread not to allow IO. + base::ThreadRestrictions::SetIOAllowed(false); + + // Start the D-Bus thread. + dbus_thread_.reset(new base::Thread("D-Bus Thread")); + base::Thread::Options thread_options; + thread_options.message_loop_type = MessageLoop::TYPE_IO; + ASSERT_TRUE(dbus_thread_->StartWithOptions(thread_options)); + + // Start the test service, using the D-Bus thread. + dbus::TestService::Options options; + options.dbus_task_runner = dbus_thread_->message_loop_proxy(); + test_service_.reset(new dbus::TestService(options)); + ASSERT_TRUE(test_service_->StartService()); + ASSERT_TRUE(test_service_->WaitUntilServiceIsStarted()); + ASSERT_TRUE(test_service_->HasDBusThread()); + + // Create the client, using the D-Bus thread. + dbus::Bus::Options bus_options; + bus_options.bus_type = dbus::Bus::SESSION; + bus_options.connection_type = dbus::Bus::PRIVATE; + bus_options.dbus_task_runner = dbus_thread_->message_loop_proxy(); + bus_ = new dbus::Bus(bus_options); + ASSERT_TRUE(bus_->HasDBusThread()); + + object_manager_ = bus_->GetObjectManager( + "org.chromium.TestService", + dbus::ObjectPath("/org/chromium/TestService")); + object_manager_->RegisterInterface("org.chromium.TestInterface", this); + + object_manager_->GetManagedObjects(); + WaitForObject(); + } + + virtual void TearDown() { + bus_->ShutdownOnDBusThreadAndBlock(); + + // Shut down the service. + test_service_->ShutdownAndBlock(); + + // Reset to the default. + base::ThreadRestrictions::SetIOAllowed(true); + + // Stopping a thread is considered an IO operation, so do this after + // allowing IO. + test_service_->Stop(); + } + + void MethodCallback(dbus::Response* response) { + method_callback_called_ = true; + message_loop_.Quit(); + } + +protected: + // Called when an object is added. + void ObjectAdded(const dbus::ObjectPath& object_path, + const std::string& interface_name) OVERRIDE { + added_objects_.push_back(std::make_pair(object_path, interface_name)); + message_loop_.Quit(); + } + + // Called when an object is removed. + void ObjectRemoved(const dbus::ObjectPath& object_path, + const std::string& interface_name) OVERRIDE { + removed_objects_.push_back(std::make_pair(object_path, interface_name)); + message_loop_.Quit(); + } + + // Called when a property value is updated. + void OnPropertyChanged(const dbus::ObjectPath& object_path, + const std::string& name) { + updated_properties_.push_back(name); + message_loop_.Quit(); + } + + static const size_t kExpectedObjects = 1; + static const size_t kExpectedProperties = 4; + + void WaitForObject() { + while (added_objects_.size() < kExpectedObjects || + updated_properties_.size() < kExpectedProperties) + message_loop_.Run(); + for (size_t i = 0; i < kExpectedObjects; ++i) + added_objects_.erase(added_objects_.begin()); + for (size_t i = 0; i < kExpectedProperties; ++i) + updated_properties_.erase(updated_properties_.begin()); + } + + void WaitForRemoveObject() { + while (removed_objects_.size() < kExpectedObjects) + message_loop_.Run(); + for (size_t i = 0; i < kExpectedObjects; ++i) + removed_objects_.erase(removed_objects_.begin()); + } + + void WaitForMethodCallback() { + message_loop_.Run(); + method_callback_called_ = false; + } + + void PerformAction(const std::string& action, + const dbus::ObjectPath& object_path) { + dbus::ObjectProxy* object_proxy = bus_->GetObjectProxy( + "org.chromium.TestService", + dbus::ObjectPath("/org/chromium/TestObject")); + + dbus::MethodCall method_call("org.chromium.TestInterface", "PerformAction"); + dbus::MessageWriter writer(&method_call); + writer.AppendString(action); + writer.AppendObjectPath(object_path); + + object_proxy->CallMethod(&method_call, + dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + base::Bind(&ObjectManagerTest::MethodCallback, + base::Unretained(this))); + WaitForMethodCallback(); + } + + MessageLoop message_loop_; + scoped_ptr<base::Thread> dbus_thread_; + scoped_refptr<dbus::Bus> bus_; + dbus::ObjectManager* object_manager_; + scoped_ptr<dbus::TestService> test_service_; + + std::vector<std::pair<dbus::ObjectPath, std::string> > added_objects_; + std::vector<std::pair<dbus::ObjectPath, std::string> > removed_objects_; + std::vector<std::string> updated_properties_; + + bool method_callback_called_; +}; + + +TEST_F(ObjectManagerTest, InitialObject) { + dbus::ObjectProxy* object_proxy = object_manager_->GetObjectProxy( + dbus::ObjectPath("/org/chromium/TestObject")); + EXPECT_TRUE(object_proxy != NULL); + + Properties* properties = static_cast<Properties*>( + object_manager_->GetProperties( + dbus::ObjectPath("/org/chromium/TestObject"), + "org.chromium.TestInterface")); + EXPECT_TRUE(properties != NULL); + + EXPECT_EQ("TestService", properties->name.value()); + EXPECT_EQ(10, properties->version.value()); + + std::vector<std::string> methods = properties->methods.value(); + ASSERT_EQ(4U, methods.size()); + EXPECT_EQ("Echo", methods[0]); + EXPECT_EQ("SlowEcho", methods[1]); + EXPECT_EQ("AsyncEcho", methods[2]); + EXPECT_EQ("BrokenMethod", methods[3]); + + std::vector<dbus::ObjectPath> objects = properties->objects.value(); + ASSERT_EQ(1U, objects.size()); + EXPECT_EQ(dbus::ObjectPath("/TestObjectPath"), objects[0]); +} + +TEST_F(ObjectManagerTest, UnknownObjectProxy) { + dbus::ObjectProxy* object_proxy = object_manager_->GetObjectProxy( + dbus::ObjectPath("/org/chromium/UnknownObject")); + EXPECT_TRUE(object_proxy == NULL); +} + +TEST_F(ObjectManagerTest, UnknownObjectProperties) { + Properties* properties = static_cast<Properties*>( + object_manager_->GetProperties( + dbus::ObjectPath("/org/chromium/UnknownObject"), + "org.chromium.TestInterface")); + EXPECT_TRUE(properties == NULL); +} + +TEST_F(ObjectManagerTest, UnknownInterfaceProperties) { + Properties* properties = static_cast<Properties*>( + object_manager_->GetProperties( + dbus::ObjectPath("/org/chromium/TestObject"), + "org.chromium.UnknownService")); + EXPECT_TRUE(properties == NULL); +} + +TEST_F(ObjectManagerTest, GetObjects) { + std::vector<dbus::ObjectPath> object_paths = object_manager_->GetObjects(); + ASSERT_EQ(1U, object_paths.size()); + EXPECT_EQ(dbus::ObjectPath("/org/chromium/TestObject"), object_paths[0]); +} + +TEST_F(ObjectManagerTest, GetObjectsWithInterface) { + std::vector<dbus::ObjectPath> object_paths = + object_manager_->GetObjectsWithInterface("org.chromium.TestInterface"); + ASSERT_EQ(1U, object_paths.size()); + EXPECT_EQ(dbus::ObjectPath("/org/chromium/TestObject"), object_paths[0]); +} + +TEST_F(ObjectManagerTest, GetObjectsWithUnknownInterface) { + std::vector<dbus::ObjectPath> object_paths = + object_manager_->GetObjectsWithInterface("org.chromium.UnknownService"); + EXPECT_EQ(0U, object_paths.size()); +} + +TEST_F(ObjectManagerTest, SameObject) { + dbus::ObjectManager* object_manager = bus_->GetObjectManager( + "org.chromium.TestService", + dbus::ObjectPath("/org/chromium/TestService")); + EXPECT_EQ(object_manager_, object_manager); +} + +TEST_F(ObjectManagerTest, DifferentObjectForService) { + dbus::ObjectManager* object_manager = bus_->GetObjectManager( + "org.chromium.DifferentService", + dbus::ObjectPath("/org/chromium/TestService")); + EXPECT_NE(object_manager_, object_manager); +} + +TEST_F(ObjectManagerTest, DifferentObjectForPath) { + dbus::ObjectManager* object_manager = bus_->GetObjectManager( + "org.chromium.TestService", + dbus::ObjectPath("/org/chromium/DifferentService")); + EXPECT_NE(object_manager_, object_manager); +} + +TEST_F(ObjectManagerTest, SecondObject) { + PerformAction("AddObject", dbus::ObjectPath("/org/chromium/SecondObject")); + WaitForObject(); + + dbus::ObjectProxy* object_proxy = object_manager_->GetObjectProxy( + dbus::ObjectPath("/org/chromium/SecondObject")); + EXPECT_TRUE(object_proxy != NULL); + + Properties* properties = static_cast<Properties*>( + object_manager_->GetProperties( + dbus::ObjectPath("/org/chromium/SecondObject"), + "org.chromium.TestInterface")); + EXPECT_TRUE(properties != NULL); + + std::vector<dbus::ObjectPath> object_paths = object_manager_->GetObjects(); + ASSERT_EQ(2U, object_paths.size()); + + std::sort(object_paths.begin(), object_paths.end()); + EXPECT_EQ(dbus::ObjectPath("/org/chromium/SecondObject"), object_paths[0]); + EXPECT_EQ(dbus::ObjectPath("/org/chromium/TestObject"), object_paths[1]); + + object_paths = + object_manager_->GetObjectsWithInterface("org.chromium.TestInterface"); + ASSERT_EQ(2U, object_paths.size()); + + std::sort(object_paths.begin(), object_paths.end()); + EXPECT_EQ(dbus::ObjectPath("/org/chromium/SecondObject"), object_paths[0]); + EXPECT_EQ(dbus::ObjectPath("/org/chromium/TestObject"), object_paths[1]); +} + +TEST_F(ObjectManagerTest, RemoveSecondObject) { + PerformAction("AddObject", dbus::ObjectPath("/org/chromium/SecondObject")); + WaitForObject(); + + std::vector<dbus::ObjectPath> object_paths = object_manager_->GetObjects(); + ASSERT_EQ(2U, object_paths.size()); + + PerformAction("RemoveObject", dbus::ObjectPath("/org/chromium/SecondObject")); + WaitForRemoveObject(); + + dbus::ObjectProxy* object_proxy = object_manager_->GetObjectProxy( + dbus::ObjectPath("/org/chromium/SecondObject")); + EXPECT_TRUE(object_proxy == NULL); + + Properties* properties = static_cast<Properties*>( + object_manager_->GetProperties( + dbus::ObjectPath("/org/chromium/SecondObject"), + "org.chromium.TestInterface")); + EXPECT_TRUE(properties == NULL); + + object_paths = object_manager_->GetObjects(); + ASSERT_EQ(1U, object_paths.size()); + EXPECT_EQ(dbus::ObjectPath("/org/chromium/TestObject"), object_paths[0]); + + object_paths = + object_manager_->GetObjectsWithInterface("org.chromium.TestInterface"); + ASSERT_EQ(1U, object_paths.size()); + EXPECT_EQ(dbus::ObjectPath("/org/chromium/TestObject"), object_paths[0]); +} diff --git a/dbus/property_unittest.cc b/dbus/property_unittest.cc index 0172517..98b5cfc 100644 --- a/dbus/property_unittest.cc +++ b/dbus/property_unittest.cc @@ -35,7 +35,7 @@ class PropertyTest : public testing::Test { Properties(dbus::ObjectProxy* object_proxy, PropertyChangedCallback property_changed_callback) : dbus::PropertySet(object_proxy, - "org.chromium.TestService", + "org.chromium.TestInterface", property_changed_callback) { RegisterProperty("Name", &name); RegisterProperty("Version", &version); diff --git a/dbus/test_service.cc b/dbus/test_service.cc index fcc34121..4b739bf 100644 --- a/dbus/test_service.cc +++ b/dbus/test_service.cc @@ -10,6 +10,7 @@ #include "dbus/bus.h" #include "dbus/exported_object.h" #include "dbus/message.h" +#include "dbus/object_manager.h" #include "dbus/object_path.h" #include "dbus/property.h" @@ -22,8 +23,9 @@ void EmptyCallback(bool /* success */) { namespace dbus { -// Echo, SlowEcho, AsyncEcho, BrokenMethod, GetAll, Get, Set. -const int TestService::kNumMethodsToExport = 7; +// Echo, SlowEcho, AsyncEcho, BrokenMethod, GetAll, Get, Set, PerformAction, +// GetManagedObjects. +const int TestService::kNumMethodsToExport = 9; TestService::Options::Options() { } @@ -204,6 +206,15 @@ void TestService::Run(MessageLoop* message_loop) { ++num_methods; exported_object_->ExportMethod( + "org.chromium.TestInterface", + "PerformAction", + base::Bind(&TestService::PerformAction, + base::Unretained(this)), + base::Bind(&TestService::OnExported, + base::Unretained(this))); + ++num_methods; + + exported_object_->ExportMethod( kPropertiesInterface, kPropertiesGetAll, base::Bind(&TestService::GetAllProperties, @@ -230,6 +241,18 @@ void TestService::Run(MessageLoop* message_loop) { base::Unretained(this))); ++num_methods; + exported_object_manager_ = bus_->GetExportedObject( + dbus::ObjectPath("/org/chromium/TestService")); + + exported_object_manager_->ExportMethod( + kObjectManagerInterface, + kObjectManagerGetManagedObjects, + base::Bind(&TestService::GetManagedObjects, + base::Unretained(this)), + base::Bind(&TestService::OnExported, + base::Unretained(this))); + ++num_methods; + // Just print an error message as we don't want to crash tests. // Tests will fail at a call to WaitUntilServiceIsStarted(). if (num_methods != kNumMethodsToExport) { @@ -289,59 +312,10 @@ void TestService::GetAllProperties( return; } - // The properties response is a dictionary of strings identifying the - // property and a variant containing the property value. We return all - // of the properties, thus the response is: - // - // { - // "Name": Variant<"TestService">, - // "Version": Variant<10>, - // "Methods": Variant<["Echo", "SlowEcho", "AsyncEcho", "BrokenMethod"]>, - // "Objects": Variant<[objectpath:"/TestObjectPath"]> - // ] - scoped_ptr<Response> response = Response::FromMethodCall(method_call); MessageWriter writer(response.get()); - MessageWriter array_writer(NULL); - MessageWriter dict_entry_writer(NULL); - MessageWriter variant_writer(NULL); - MessageWriter variant_array_writer(NULL); - - writer.OpenArray("{sv}", &array_writer); - - array_writer.OpenDictEntry(&dict_entry_writer); - dict_entry_writer.AppendString("Name"); - dict_entry_writer.AppendVariantOfString("TestService"); - array_writer.CloseContainer(&dict_entry_writer); - - array_writer.OpenDictEntry(&dict_entry_writer); - dict_entry_writer.AppendString("Version"); - dict_entry_writer.AppendVariantOfInt16(10); - array_writer.CloseContainer(&dict_entry_writer); - - array_writer.OpenDictEntry(&dict_entry_writer); - dict_entry_writer.AppendString("Methods"); - dict_entry_writer.OpenVariant("as", &variant_writer); - variant_writer.OpenArray("s", &variant_array_writer); - variant_array_writer.AppendString("Echo"); - variant_array_writer.AppendString("SlowEcho"); - variant_array_writer.AppendString("AsyncEcho"); - variant_array_writer.AppendString("BrokenMethod"); - variant_writer.CloseContainer(&variant_array_writer); - dict_entry_writer.CloseContainer(&variant_writer); - array_writer.CloseContainer(&dict_entry_writer); - - array_writer.OpenDictEntry(&dict_entry_writer); - dict_entry_writer.AppendString("Objects"); - dict_entry_writer.OpenVariant("ao", &variant_writer); - variant_writer.OpenArray("o", &variant_array_writer); - variant_array_writer.AppendObjectPath(dbus::ObjectPath("/TestObjectPath")); - variant_writer.CloseContainer(&variant_array_writer); - dict_entry_writer.CloseContainer(&variant_writer); - array_writer.CloseContainer(&dict_entry_writer); - - writer.CloseContainer(&array_writer); + AddPropertiesToWriter(&writer); response_sender.Run(response.Pass()); } @@ -452,6 +426,171 @@ void TestService::SetProperty( response_sender.Run(Response::FromMethodCall(method_call)); } +void TestService::PerformAction( + MethodCall* method_call, + dbus::ExportedObject::ResponseSender response_sender) { + MessageReader reader(method_call); + std::string action; + dbus::ObjectPath object_path; + if (!reader.PopString(&action) || !reader.PopObjectPath(&object_path)) { + response_sender.Run(scoped_ptr<dbus::Response>()); + return; + } + + if (action == "AddObject") + AddObject(object_path); + else if (action == "RemoveObject") + RemoveObject(object_path); + + scoped_ptr<Response> response = Response::FromMethodCall(method_call); + response_sender.Run(response.Pass()); +} + +void TestService::GetManagedObjects( + MethodCall* method_call, + dbus::ExportedObject::ResponseSender response_sender) { + scoped_ptr<Response> response = Response::FromMethodCall(method_call); + MessageWriter writer(response.get()); + + // The managed objects response is a dictionary of object paths identifying + // the object(s) with a dictionary of strings identifying the interface(s) + // they implement and then a dictionary of property values. + // + // Thus this looks something like: + // + // { + // "/org/chromium/TestObject": { + // "org.chromium.TestInterface": { /* Properties */ } + // } + // } + + + MessageWriter array_writer(NULL); + MessageWriter dict_entry_writer(NULL); + MessageWriter object_array_writer(NULL); + MessageWriter object_dict_entry_writer(NULL); + + writer.OpenArray("{oa{sa{sv}}}", &array_writer); + + array_writer.OpenDictEntry(&dict_entry_writer); + dict_entry_writer.AppendObjectPath( + dbus::ObjectPath("/org/chromium/TestObject")); + dict_entry_writer.OpenArray("{sa{sv}}", &object_array_writer); + + object_array_writer.OpenDictEntry(&object_dict_entry_writer); + object_dict_entry_writer.AppendString("org.chromium.TestInterface"); + AddPropertiesToWriter(&object_dict_entry_writer); + object_array_writer.CloseContainer(&object_dict_entry_writer); + + dict_entry_writer.CloseContainer(&object_array_writer); + + array_writer.CloseContainer(&dict_entry_writer); + writer.CloseContainer(&array_writer); + + response_sender.Run(response.Pass()); +} + +void TestService::AddPropertiesToWriter(MessageWriter* writer) +{ + // The properties response is a dictionary of strings identifying the + // property and a variant containing the property value. We return all + // of the properties, thus the response is: + // + // { + // "Name": Variant<"TestService">, + // "Version": Variant<10>, + // "Methods": Variant<["Echo", "SlowEcho", "AsyncEcho", "BrokenMethod"]>, + // "Objects": Variant<[objectpath:"/TestObjectPath"]> + // ] + + MessageWriter array_writer(NULL); + MessageWriter dict_entry_writer(NULL); + MessageWriter variant_writer(NULL); + MessageWriter variant_array_writer(NULL); + + writer->OpenArray("{sv}", &array_writer); + + array_writer.OpenDictEntry(&dict_entry_writer); + dict_entry_writer.AppendString("Name"); + dict_entry_writer.AppendVariantOfString("TestService"); + array_writer.CloseContainer(&dict_entry_writer); + + array_writer.OpenDictEntry(&dict_entry_writer); + dict_entry_writer.AppendString("Version"); + dict_entry_writer.AppendVariantOfInt16(10); + array_writer.CloseContainer(&dict_entry_writer); + + array_writer.OpenDictEntry(&dict_entry_writer); + dict_entry_writer.AppendString("Methods"); + dict_entry_writer.OpenVariant("as", &variant_writer); + variant_writer.OpenArray("s", &variant_array_writer); + variant_array_writer.AppendString("Echo"); + variant_array_writer.AppendString("SlowEcho"); + variant_array_writer.AppendString("AsyncEcho"); + variant_array_writer.AppendString("BrokenMethod"); + variant_writer.CloseContainer(&variant_array_writer); + dict_entry_writer.CloseContainer(&variant_writer); + array_writer.CloseContainer(&dict_entry_writer); + + array_writer.OpenDictEntry(&dict_entry_writer); + dict_entry_writer.AppendString("Objects"); + dict_entry_writer.OpenVariant("ao", &variant_writer); + variant_writer.OpenArray("o", &variant_array_writer); + variant_array_writer.AppendObjectPath(dbus::ObjectPath("/TestObjectPath")); + variant_writer.CloseContainer(&variant_array_writer); + dict_entry_writer.CloseContainer(&variant_writer); + array_writer.CloseContainer(&dict_entry_writer); + + writer->CloseContainer(&array_writer); +} + +void TestService::AddObject(const dbus::ObjectPath& object_path) { + message_loop()->PostTask( + FROM_HERE, + base::Bind(&TestService::AddObjectInternal, + base::Unretained(this), + object_path)); +} + +void TestService::AddObjectInternal(const dbus::ObjectPath& object_path) { + dbus::Signal signal(kObjectManagerInterface, kObjectManagerInterfacesAdded); + dbus::MessageWriter writer(&signal); + writer.AppendObjectPath(object_path); + + MessageWriter array_writer(NULL); + MessageWriter dict_entry_writer(NULL); + + writer.OpenArray("{sa{sv}}", &array_writer); + array_writer.OpenDictEntry(&dict_entry_writer); + dict_entry_writer.AppendString("org.chromium.TestInterface"); + AddPropertiesToWriter(&dict_entry_writer); + array_writer.CloseContainer(&dict_entry_writer); + writer.CloseContainer(&array_writer); + + exported_object_manager_->SendSignal(&signal); +} + +void TestService::RemoveObject(const dbus::ObjectPath& object_path) { + message_loop()->PostTask( + FROM_HERE, + base::Bind(&TestService::RemoveObjectInternal, + base::Unretained(this), + object_path)); +} + +void TestService::RemoveObjectInternal(const dbus::ObjectPath& object_path) { + dbus::Signal signal(kObjectManagerInterface, kObjectManagerInterfacesRemoved); + dbus::MessageWriter writer(&signal); + + writer.AppendObjectPath(object_path); + + std::vector<std::string> interfaces; + interfaces.push_back("org.chromium.TestInterface"); + writer.AppendArrayOfStrings(interfaces); + + exported_object_manager_->SendSignal(&signal); +} + void TestService::SendPropertyChangedSignal(const std::string& name) { message_loop()->PostTask( FROM_HERE, @@ -463,7 +602,7 @@ void TestService::SendPropertyChangedSignal(const std::string& name) { void TestService::SendPropertyChangedSignalInternal(const std::string& name) { dbus::Signal signal(kPropertiesInterface, kPropertiesChanged); dbus::MessageWriter writer(&signal); - writer.AppendString("org.chromium.TestService"); + writer.AppendString("org.chromium.TestInterface"); MessageWriter array_writer(NULL); MessageWriter dict_entry_writer(NULL); diff --git a/dbus/test_service.h b/dbus/test_service.h index 562e7c4..b9e14b5 100644 --- a/dbus/test_service.h +++ b/dbus/test_service.h @@ -19,6 +19,7 @@ namespace dbus { class Bus; class MethodCall; +class MessageWriter; class Response; // The test service is used for end-to-end tests. The service runs in a @@ -134,6 +135,25 @@ class TestService : public base::Thread { void SetProperty(MethodCall* method_call, dbus::ExportedObject::ResponseSender response_sender); + // Performs an action for testing. + void PerformAction(MethodCall* method_call, + dbus::ExportedObject::ResponseSender response_sender); + + // Object Manager: returns the set of objects and properties. + void GetManagedObjects(MethodCall* method_call, + dbus::ExportedObject::ResponseSender response_sender); + + // Add a properties dictionary to a message writer. + void AddPropertiesToWriter(MessageWriter* writer); + + // Add a new object to the manager. + void AddObject(const dbus::ObjectPath& object_path); + void AddObjectInternal(const dbus::ObjectPath& object_path); + + // Remove an object from the manager. + void RemoveObject(const dbus::ObjectPath& object_path); + void RemoveObjectInternal(const dbus::ObjectPath& object_path); + // Sends a property changed signal for the name property. void SendPropertyChangedSignal(const std::string& name); @@ -153,6 +173,7 @@ class TestService : public base::Thread { scoped_refptr<Bus> bus_; ExportedObject* exported_object_; + ExportedObject* exported_object_manager_; }; } // namespace dbus |