summaryrefslogtreecommitdiffstats
path: root/dbus
diff options
context:
space:
mode:
authorkeybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-25 18:20:08 +0000
committerkeybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-25 18:20:08 +0000
commit9cc40cb2061532d345a6ec925e97563631132e9e (patch)
treeecfb2c793420271b3b172f27ecebc66ae966bb65 /dbus
parentdbc220a0550880c4d35d75a57f10663d3e1ed240 (diff)
downloadchromium_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.cc40
-rw-r--r--dbus/bus.h39
-rw-r--r--dbus/dbus.gyp5
-rw-r--r--dbus/mock_bus.h2
-rw-r--r--dbus/mock_object_manager.cc18
-rw-r--r--dbus/mock_object_manager.h42
-rw-r--r--dbus/object_manager.cc295
-rw-r--r--dbus/object_manager.h316
-rw-r--r--dbus/object_manager_unittest.cc339
-rw-r--r--dbus/property_unittest.cc2
-rw-r--r--dbus/test_service.cc245
-rw-r--r--dbus/test_service.h21
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();
diff --git a/dbus/bus.h b/dbus/bus.h
index 1641699..ec6bef1 100644
--- a/dbus/bus.h
+++ b/dbus/bus.h
@@ -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