summaryrefslogtreecommitdiffstats
path: root/dbus
diff options
context:
space:
mode:
authorkeybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-02-15 04:21:08 +0000
committerkeybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-02-15 04:21:08 +0000
commitcf910da2d3cb23ac300968212dc8a196a64b2726 (patch)
treeb7975c349ab6aeccee275e630b2ece900000d387 /dbus
parentc401049c2ed235c156292b632f43198c44641b65 (diff)
downloadchromium_src-cf910da2d3cb23ac300968212dc8a196a64b2726.zip
chromium_src-cf910da2d3cb23ac300968212dc8a196a64b2726.tar.gz
chromium_src-cf910da2d3cb23ac300968212dc8a196a64b2726.tar.bz2
dbus: add Property handling for clients
D-Bus properties can be tricky to handle due to their reliance on variants, and since they use a common interface, result in a lot of copy and paste code to deal with. Add an API that simplifies matters somewhat; detailed documentation is in dbus/property.h, but fundamentally you add a struct Properties to the client implementation derived from dbus::PropertySet, and in it declare members of dbus::Property<property type> and connect them in the constructor with RegisterProperty(name, ptr). The API works on two levels, from a higher-level each member of the structure is a type-safe way to obtain the current value of the property, update the value, and set a new value. From the lower-level, it uses a generic reader/writer based interface so that the parent structure's GetAll and signal handling methods can update the values without requiring knowledge of the contained type. BUG=chromium:109194 TEST=unit tests included in CL change-Id: I111b9e60a2c6c35edd9e0ea9f6976928c6c6474b Review URL: http://codereview.chromium.org/9380053 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@122039 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'dbus')
-rw-r--r--dbus/dbus.gyp3
-rw-r--r--dbus/property.cc425
-rw-r--r--dbus/property.h421
-rw-r--r--dbus/property_unittest.cc199
-rw-r--r--dbus/test_service.cc188
-rw-r--r--dbus/test_service.h20
6 files changed, 1253 insertions, 3 deletions
diff --git a/dbus/dbus.gyp b/dbus/dbus.gyp
index 8a3cb22..296ac53 100644
--- a/dbus/dbus.gyp
+++ b/dbus/dbus.gyp
@@ -29,6 +29,8 @@
'object_path.h',
'object_proxy.cc',
'object_proxy.h',
+ 'property.cc',
+ 'property.h',
'scoped_dbus_error.h',
],
},
@@ -83,6 +85,7 @@
'end_to_end_sync_unittest.cc',
'message_unittest.cc',
'mock_unittest.cc',
+ 'property_unittest.cc',
'test_service.cc',
'test_service.h',
],
diff --git a/dbus/property.cc b/dbus/property.cc
new file mode 100644
index 0000000..9d2adaf
--- /dev/null
+++ b/dbus/property.cc
@@ -0,0 +1,425 @@
+// Copyright (c) 2012 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/property.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/logging.h"
+
+#include "dbus/message.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+
+namespace dbus {
+
+//
+// PropertyBase implementation.
+//
+
+void PropertyBase::Init(PropertySet* property_set, const std::string& name) {
+ DCHECK(!property_set_);
+ property_set_ = property_set;
+ name_ = name;
+}
+
+
+//
+// PropertySet implementation.
+//
+
+PropertySet::PropertySet(ObjectProxy* object_proxy,
+ const std::string& interface,
+ PropertyChangedCallback property_changed_callback)
+ : object_proxy_(object_proxy),
+ interface_(interface),
+ property_changed_callback_(property_changed_callback),
+ weak_ptr_factory_(this) {}
+
+PropertySet::~PropertySet() {
+}
+
+void PropertySet::RegisterProperty(const std::string& name,
+ PropertyBase* property) {
+ property->Init(this, name);
+ properties_map_[name] = property;
+}
+
+void PropertySet::ConnectSignals() {
+ DCHECK(object_proxy_);
+ object_proxy_->ConnectToSignal(
+ kPropertiesInterface,
+ kPropertiesChanged,
+ base::Bind(&PropertySet::ChangedReceived,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&PropertySet::ChangedConnected,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+
+void PropertySet::ChangedReceived(Signal* signal) {
+ VLOG(1) << "ChangedRecieved";
+ DCHECK(signal);
+
+ MessageReader reader(signal);
+
+ std::string interface;
+ if (!reader.PopString(&interface)) {
+ LOG(WARNING) << "Property changed signal has wrong parameters: "
+ << "expected interface name: " << signal->ToString();
+ return;
+ }
+
+ if (interface != this->interface())
+ return;
+
+ if (!UpdatePropertiesFromReader(&reader)) {
+ LOG(WARNING) << "Property changed signal has wrong parameters: "
+ << "expected dictionary: " << signal->ToString();
+ }
+
+ // TODO(keybuk): dbus properties api has invalidated properties array
+ // on the end, we don't handle this right now because I don't know of
+ // any service that sends it - or what they expect us to do with it.
+ // Add later when we need it.
+}
+
+void PropertySet::ChangedConnected(const std::string& interface_name,
+ const std::string& signal_name,
+ bool success) {
+ LOG_IF(WARNING, !success) << "Failed to connect to " << signal_name
+ << "signal.";
+}
+
+
+void PropertySet::GetAll() {
+ MethodCall method_call(kPropertiesInterface, kPropertiesGetAll);
+ MessageWriter writer(&method_call);
+ writer.AppendString(interface());
+
+ DCHECK(object_proxy_);
+ object_proxy_->CallMethod(&method_call,
+ ObjectProxy::TIMEOUT_USE_DEFAULT,
+ base::Bind(&PropertySet::OnGetAll,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void PropertySet::OnGetAll(Response* response) {
+ VLOG(1) << "OnGetAll";
+ if (!response) {
+ LOG(WARNING) << "GetAll request failed.";
+ return;
+ }
+
+ MessageReader reader(response);
+ if (!UpdatePropertiesFromReader(&reader)) {
+ LOG(WARNING) << "GetAll response has wrong parameters: "
+ << "expected dictionary: " << response->ToString();
+ }
+}
+
+
+bool PropertySet::UpdatePropertiesFromReader(MessageReader* reader) {
+ DCHECK(reader);
+ MessageReader array_reader(NULL);
+ if (!reader->PopArray(&array_reader))
+ return false;
+
+ while (array_reader.HasMoreData()) {
+ MessageReader dict_entry_reader(NULL);
+ if (!array_reader.PopDictEntry(&dict_entry_reader))
+ continue;
+
+ if (!UpdatePropertyFromReader(&dict_entry_reader))
+ continue;
+ }
+
+ return true;
+}
+
+bool PropertySet::UpdatePropertyFromReader(MessageReader* reader) {
+ DCHECK(reader);
+
+ std::string name;
+ if (!reader->PopString(&name))
+ return false;
+
+ PropertiesMap::iterator it = properties_map_.find(name);
+ if (it == properties_map_.end())
+ return false;
+
+ PropertyBase* property = it->second;
+ if (property->PopValueFromReader(reader)) {
+ NotifyPropertyChanged(name);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+void PropertySet::NotifyPropertyChanged(const std::string& name) {
+ if (!property_changed_callback_.is_null())
+ property_changed_callback_.Run(name);
+}
+
+//
+// Property<Byte> specialization.
+//
+
+template <>
+Property<uint8>::Property() : value_(0),
+ weak_ptr_factory_(this) {
+}
+
+template <>
+bool Property<uint8>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfByte(&value_);
+}
+
+template <>
+void Property<uint8>::AppendToWriter(MessageWriter* writer,
+ const uint8& value) {
+ writer->AppendVariantOfByte(value);
+}
+
+//
+// Property<bool> specialization.
+//
+
+template <>
+Property<bool>::Property() : value_(false),
+ weak_ptr_factory_(this) {
+}
+
+template <>
+bool Property<bool>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfBool(&value_);
+}
+
+template <>
+void Property<bool>::AppendToWriter(MessageWriter* writer,
+ const bool& value) {
+ writer->AppendVariantOfBool(value);
+}
+
+//
+// Property<int16> specialization.
+//
+
+template <>
+Property<int16>::Property() : value_(0),
+ weak_ptr_factory_(this) {
+}
+
+template <>
+bool Property<int16>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfInt16(&value_);
+}
+
+template <>
+void Property<int16>::AppendToWriter(MessageWriter* writer,
+ const int16& value) {
+ writer->AppendVariantOfInt16(value);
+}
+
+//
+// Property<uint16> specialization.
+//
+
+template <>
+Property<uint16>::Property() : value_(0),
+ weak_ptr_factory_(this) {
+}
+
+template <>
+bool Property<uint16>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfUint16(&value_);
+}
+
+template <>
+void Property<uint16>::AppendToWriter(MessageWriter* writer,
+ const uint16& value) {
+ writer->AppendVariantOfUint16(value);
+}
+
+//
+// Property<int32> specialization.
+//
+
+template <>
+Property<int32>::Property() : value_(0),
+ weak_ptr_factory_(this) {
+}
+
+template <>
+bool Property<int32>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfInt32(&value_);
+}
+
+template <>
+void Property<int32>::AppendToWriter(MessageWriter* writer,
+ const int32& value) {
+ writer->AppendVariantOfInt32(value);
+}
+
+//
+// Property<uint32> specialization.
+//
+
+template <>
+Property<uint32>::Property() : value_(0),
+ weak_ptr_factory_(this) {
+}
+
+template <>
+bool Property<uint32>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfUint32(&value_);
+}
+
+template <>
+void Property<uint32>::AppendToWriter(MessageWriter* writer,
+ const uint32& value) {
+ writer->AppendVariantOfUint32(value);
+}
+
+//
+// Property<int64> specialization.
+//
+
+template <>
+Property<int64>::Property() : value_(0),
+ weak_ptr_factory_(this) {
+}
+
+template <>
+bool Property<int64>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfInt64(&value_);
+}
+
+template <>
+void Property<int64>::AppendToWriter(MessageWriter* writer,
+ const int64& value) {
+ writer->AppendVariantOfInt64(value);
+}
+
+//
+// Property<uint64> specialization.
+//
+
+template <>
+Property<uint64>::Property() : value_(0),
+ weak_ptr_factory_(this) {
+}
+
+template <>
+bool Property<uint64>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfUint64(&value_);
+}
+
+template <>
+void Property<uint64>::AppendToWriter(MessageWriter* writer,
+ const uint64& value) {
+ writer->AppendVariantOfUint64(value);
+}
+
+//
+// Property<double> specialization.
+//
+
+template <>
+Property<double>::Property() : value_(0.0),
+ weak_ptr_factory_(this) {
+}
+
+template <>
+bool Property<double>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfDouble(&value_);
+}
+
+template <>
+void Property<double>::AppendToWriter(MessageWriter* writer,
+ const double& value) {
+ writer->AppendVariantOfDouble(value);
+}
+
+//
+// Property<std::string> specialization.
+//
+
+template <>
+bool Property<std::string>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfString(&value_);
+}
+
+template <>
+void Property<std::string>::AppendToWriter(MessageWriter* writer,
+ const std::string& value) {
+ writer->AppendVariantOfString(value);
+}
+
+//
+// Property<ObjectPath> specialization.
+//
+
+template <>
+bool Property<ObjectPath>::PopValueFromReader(MessageReader* reader) {
+ return reader->PopVariantOfObjectPath(&value_);
+}
+
+template <>
+void Property<ObjectPath>::AppendToWriter(MessageWriter* writer,
+ const ObjectPath& value) {
+ writer->AppendVariantOfObjectPath(value);
+}
+
+//
+// Property<std::vector<std::string> > specialization.
+//
+
+template <>
+bool Property<std::vector<std::string> >::PopValueFromReader(
+ MessageReader* reader) {
+ MessageReader variant_reader(NULL);
+ if (!reader->PopVariant(&variant_reader))
+ return false;
+
+ return variant_reader.PopArrayOfStrings(&value_);
+}
+
+template <>
+void Property<std::vector<std::string> >::AppendToWriter(
+ MessageWriter* writer,
+ const std::vector<std::string>& value) {
+ MessageWriter variant_writer(NULL);
+ writer->OpenVariant("as", &variant_writer);
+ variant_writer.AppendArrayOfStrings(value);
+ writer->CloseContainer(&variant_writer);
+}
+
+//
+// Property<std::vector<ObjectPath> > specialization.
+//
+
+template <>
+bool Property<std::vector<ObjectPath> >::PopValueFromReader(
+ MessageReader* reader) {
+ MessageReader variant_reader(NULL);
+ if (!reader->PopVariant(&variant_reader))
+ return false;
+
+ return variant_reader.PopArrayOfObjectPaths(&value_);
+}
+
+template <>
+void Property<std::vector<ObjectPath> >::AppendToWriter(
+ MessageWriter* writer,
+ const std::vector<ObjectPath>& value) {
+ MessageWriter variant_writer(NULL);
+ writer->OpenVariant("ao", &variant_writer);
+ variant_writer.AppendArrayOfObjectPaths(value);
+ writer->CloseContainer(&variant_writer);
+}
+
+} // namespace dbus
diff --git a/dbus/property.h b/dbus/property.h
new file mode 100644
index 0000000..47b118f6
--- /dev/null
+++ b/dbus/property.h
@@ -0,0 +1,421 @@
+// Copyright (c) 2012 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_PROPERTY_H_
+#define DBUS_PROPERTY_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "dbus/message.h"
+#include "dbus/object_proxy.h"
+
+// D-Bus objects frequently provide sets of properties accessed via a
+// standard interface of method calls and signals to obtain the current value,
+// set a new value and be notified of changes to the value. Unfortunately this
+// interface makes heavy use of variants and dictionaries of variants. The
+// classes defined here make dealing with properties in a type-safe manner
+// possible.
+//
+// Client implementation classes should define a Properties structure, deriving
+// from the PropertySet class defined here. This structure should contain a
+// member for each property defined as an instance of the Property<> class,
+// specifying the type to the template. Finally the structure should chain up
+// to the PropertySet constructor, and then call RegisterProperty() for each
+// property defined to associate them with their string name.
+//
+// Example:
+// class ExampleClient {
+// 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,
+// PropertyChangedCallback callback)
+// : dbus::PropertySet(object_proxy, kExampleInterface, callback) {
+// RegisterProperty("Name", &name);
+// RegisterProperty("Version", &version);
+// RegisterProperty("Parent", &parent);
+// RegisterProperty("Children", &children);
+// }
+// };
+//
+// The Properties structure requires a pointer to the object proxy of the
+// actual object to track, and after construction should have signals
+// connected to that object and initial values set by calling ConnectSignals()
+// and GetAll(). The structure should not outlive the object proxy, so it
+// is recommended that the lifecycle of both be managed together.
+//
+// Example (continued):
+//
+// typedef std::map<std::pair<dbus::ObjectProxy*,Properties*> > Object;
+// typedef std::map<dbus::ObjectPath,Object> ObjectMap;
+// ObjectMap object_map_;
+//
+// dbus::ObjectProxy* GetObjectProxy(const dbus::ObjectPath& object_path) {
+// return GetObject(object_path).first;
+// }
+//
+// Properties* GetProperties(const dbus::ObjectPath& object_path) {
+// return GetObject(object_path).second;
+// }
+//
+// Object GetObject(const dbus::ObjectPath& object_path) {
+// ObjectMap::iterator it = object_map_.find(object_path);
+// if (it != object_map_.end())
+// return it->second;
+//
+// dbus::ObjectProxy* object_proxy = bus->GetObjectProxy(...);
+// // connect signals, ect.
+//
+// Properties* properties = new Properties(
+// object_proxy,
+// base::Bind(&PropertyChanged,
+// weak_ptr_factory_.GetWeakPtr(),
+// object_path));
+// properties->ConnectSignals();
+// properties->GetAll();
+//
+// return object_map_.insert(std::make_pair(object_proxy,
+// properties).first;
+// }
+// };
+//
+// This now allows code using the client implementation to access properties
+// in a type-safe manner, and assuming the PropertyChanged callback is
+// propogated up to observers, be notified of changes. A typical access of
+// the current value of the name property would be:
+//
+// ExampleClient::Properties* p = example_client->GetProperties(object_path);
+// std::string name = p->name.value();
+//
+// Normally these values are updated from signals emitted by the remote object,
+// in case an explicit round-trip is needed to obtain the current value, the
+// Get() method can be used and indicates whether or not the value update was
+// successful. The updated value can be obtained in the callback using the
+// value() method.
+//
+// p->children.Get(base::Bind(&OnGetChildren));
+//
+// A new value can be set using the Set() method, the callback indicates
+// success only; it is up to the remote object when (and indeed if) it updates
+// the property value, and whether it emits a signal or a Get() call is
+// required to obtain it.
+//
+// p->version.Set(20, base::Bind(&OnSetVersion))
+
+namespace dbus {
+
+// D-Bus Properties interface constants, declared here rather than
+// in property.cc because template methods use them.
+const char kPropertiesInterface[] = "org.freedesktop.DBus.Properties";
+const char kPropertiesGetAll[] = "GetAll";
+const char kPropertiesGet[] = "Get";
+const char kPropertiesSet[] = "Set";
+const char kPropertiesChanged[] = "PropertiesChanged";
+
+class PropertySet;
+
+// PropertyBase is an abstract base-class consisting of the parts of
+// the Property<> template that are not type-specific, such as the
+// associated PropertySet, property name, and the type-unsafe parts
+// used by PropertySet.
+class PropertyBase {
+ public:
+ PropertyBase() : property_set_(NULL) {}
+
+ // Initializes the |property_set| and property |name| so that method
+ // calls may be made from this class. This method is called by
+ // PropertySet::RegisterProperty(), there should be no need to call it
+ // directly.
+ void Init(PropertySet* property_set, const std::string& name);
+
+ // Retrieves the name of this property, this may be useful in observers
+ // to avoid specifying the name in more than once place, e.g.
+ //
+ // void Client::PropertyChanged(const dbus::ObjectPath& object_path,
+ // const std::string &property_name) {
+ // Properties& properties = GetProperties(object_path);
+ // if (property_name == properties.version.name()) {
+ // // Handle version property changing
+ // }
+ // }
+ const std::string& name() { return name_; }
+
+ // Method used by PropertySet to retrieve the value from a MessageReader,
+ // no knowledge of the contained type is required, this method returns
+ // true if its expected type was found, false if not.
+ virtual bool PopValueFromReader(MessageReader*) = 0;
+
+ protected:
+ // Retrieves the associated property set.
+ PropertySet* property_set() { return property_set_; }
+
+ private:
+ // Pointer to the associated property set.
+ PropertySet* property_set_;
+
+ // Name of the property.
+ std::string name_;
+
+ DISALLOW_COPY_AND_ASSIGN(PropertyBase);
+};
+
+// PropertySet groups a collection of properties for a remote object
+// together into a single structure, fixing their types and name such
+// that calls made through it are type-safe.
+//
+// Clients always sub-class this to add the properties, and should always
+// provide a constructor that chains up to this and then calls
+// RegisterProperty() for each property defined.
+//
+// After creation, client code should call ConnectSignals() and most likely
+// GetAll() to seed initial values and update as changes occur.
+class PropertySet {
+ public:
+ // Callback for changes to cached values of properties, either notified
+ // via signal, or as a result of calls to Get() and GetAll(). The |name|
+ // argument specifies the name of the property changed.
+ typedef base::Callback<void(const std::string& name)> PropertyChangedCallback;
+
+ // Construct a property set, |object_proxy| specifies the proxy for the
+ // remote object that these properties are for, care should be taken to
+ // ensure that this object does not outlive the lifetime of the proxy;
+ // |interface| specifies the D-Bus interface of these properties, and
+ // |property_changed_callback| specifies the callback for when properties
+ // are changed, this may be a NULL callback.
+ PropertySet(ObjectProxy* object_proxy, const std::string& interface,
+ PropertyChangedCallback property_changed_callback);
+
+ // Destructor; we don't hold on to any references or memory that needs
+ // explicit clean-up, but clang thinks we might.
+ ~PropertySet();
+
+ // Registers a property, generally called from the subclass constructor;
+ // pass the |name| of the property as used in method calls and signals,
+ // and the pointer to the |property| member of the structure. This will
+ // call the PropertyBase::Init method.
+ void RegisterProperty(const std::string& name, PropertyBase* property);
+
+ // Call after construction to connect property change notification
+ // signals. Sub-classes may override to use different D-Bus signals.
+ void ConnectSignals();
+
+ // Methods connected by ConnectSignals() and called by dbus:: when
+ // a property is changed. Sub-classes may override if the property
+ // changed signal provides different arguments.
+ virtual void ChangedReceived(Signal*);
+ virtual void ChangedConnected(const std::string& interface_name,
+ const std::string& signal_name,
+ bool success);
+
+ // Queries the remove object for values of all properties and updates
+ // initial values. Sub-classes may override to use a different D-Bus
+ // method, or if the remote object does not support retrieving all
+ // properties, either ignore or obtain each property value individually.
+ void GetAll();
+ virtual void OnGetAll(Response* response);
+
+ // Update properties by reading an array of dictionary entries, each
+ // containing a string with the name and a variant with the value, from
+ // |message_reader|. Returns false if message in incorrect format.
+ bool UpdatePropertiesFromReader(MessageReader* reader);
+
+ // Updates a single property by reading a string with the name and a
+ // variant with the value from |message_reader|. Returns false if message
+ // in incorrect format, or property type doesn't match.
+ bool UpdatePropertyFromReader(MessageReader* reader);
+
+ // Calls the property changed callback passed to the constructor, used
+ // by sub-classes that do not call UpdatePropertiesFromReader() or
+ // UpdatePropertyFromReader(). Takes the |name| of the changed property.
+ void NotifyPropertyChanged(const std::string& name);
+
+ // Retrieves the object proxy this property set was initialized with,
+ // provided for sub-classes overriding methods that make D-Bus calls
+ // and for Property<>.
+ ObjectProxy* object_proxy() { return object_proxy_; }
+
+ // Retrieves the interface of this property set.
+ const std::string& interface() { return interface_; }
+
+ protected:
+ // Get a weak pointer to this property set, provided so that sub-classes
+ // overriding methods that make D-Bus calls may use the existing (or
+ // override) callbacks without providing their own weak pointer factory.
+ base::WeakPtr<PropertySet> GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ private:
+ // Pointer to object proxy for making method calls.
+ ObjectProxy* object_proxy_;
+
+ // Interface of property, e.g. "org.chromium.ExampleService", this is
+ // distinct from the interface of the method call itself which is the
+ // general D-Bus Properties interface "org.freedesktop.DBus.Properties".
+ std::string interface_;
+
+ // Callback for property changes.
+ PropertyChangedCallback property_changed_callback_;
+
+ // Map of properties (as PropertyBase*) defined in the structure to
+ // names as used in D-Bus method calls and signals. The base pointer
+ // restricts property access via this map to type-unsafe and non-specific
+ // actions only.
+ typedef std::map<const std::string, PropertyBase*> PropertiesMap;
+ PropertiesMap properties_map_;
+
+ // Weak pointer factory as D-Bus callbacks may last longer than these
+ // objects.
+ base::WeakPtrFactory<PropertySet> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PropertySet);
+};
+
+// Property template, this defines the type-specific and type-safe methods
+// of properties that can be accessed as members of a PropertySet structure.
+//
+// Properties provide a cached value that has an initial sensible default
+// until the reply to PropertySet::GetAll() is retrieved and is updated by
+// all calls to that method, Property<>::Get() and property changed signals
+// handled by PropertySet. It can be obtained by calling value() on the
+// property.
+//
+// It is recommended that this cached value be used where necessary, with
+// code using PropertySet::PropertyChangedCallback to be notified of changes,
+// rather than incurring a round-trip to the remote object for each property
+// access.
+//
+// Where a round-trip is necessary, the Get() method is provided. And to
+// update the remote object value, the Set() method is also provided.
+//
+// Handling of particular D-Bus types is performed via specialization,
+// typically the PopValueFromReader() and AppendToWriter() methods will need
+// to be provided, and in rare cases a constructor to provide a default value.
+// Specializations for basic D-Bus types, strings, object paths and arrays
+// are provided for you.
+template <class T>
+class Property : public PropertyBase {
+ public:
+ // Callback for Get() method, |success| indicates whether or not the
+ // value could be retrived, if true the new value can be obtained by
+ // calling value() on the property.
+ typedef base::Callback<void(bool success)> GetCallback;
+
+ // Callback for Set() method, |success| indicates whether or not the
+ // new property value was accepted by the remote object.
+ typedef base::Callback<void(bool success)> SetCallback;
+
+ Property() : weak_ptr_factory_(this) {}
+
+ // Retrieves the cached value.
+ const T& value() { return value_; }
+
+ // Requests an updated value from the remote object incurring a
+ // round-trip. |callback| will be called when the new value is available.
+ // This may not be implemented by some interfaces, and may be overriden by
+ // sub-classes if interfaces use different method calls.
+ void Get(GetCallback callback) {
+ DVLOG(1) << "Get: " << name();
+
+ MethodCall method_call(kPropertiesInterface, kPropertiesGet);
+ MessageWriter writer(&method_call);
+ writer.AppendString(property_set()->interface());
+ writer.AppendString(name());
+
+ ObjectProxy *object_proxy = property_set()->object_proxy();
+ DCHECK(object_proxy);
+ object_proxy->CallMethod(&method_call,
+ ObjectProxy::TIMEOUT_USE_DEFAULT,
+ base::Bind(&Property<T>::OnGet,
+ GetWeakPtr(),
+ callback));
+ }
+
+ // Callback for Get(), may be overriden by sub-classes if interfaces
+ // use different response arguments.
+ virtual void OnGet(SetCallback callback, Response* response) {
+ DVLOG(1) << "OnGet: " << name();
+ if (!response) {
+ LOG(WARNING) << name() << ": Get: failed.";
+ return;
+ }
+
+ MessageReader reader(response);
+ if (PopValueFromReader(&reader))
+ property_set()->NotifyPropertyChanged(name());
+
+ if (!callback.is_null())
+ callback.Run(response);
+ }
+
+ // Requests that the remote object change the property value to |value|,
+ // |callback| will be called to indicate the success or failure of the
+ // request, however the new value may not be available depending on the
+ // remote object. This method may be overriden by sub-classes if
+ // interfaces use different method calls.
+ void Set(const T& value, SetCallback callback) {
+ DVLOG(1) << "Set: " << name();
+
+ MethodCall method_call(kPropertiesInterface, kPropertiesSet);
+ MessageWriter writer(&method_call);
+ writer.AppendString(property_set()->interface());
+ writer.AppendString(name());
+ AppendToWriter(&writer, value);
+
+ ObjectProxy *object_proxy = property_set()->object_proxy();
+ DCHECK(object_proxy);
+ object_proxy->CallMethod(&method_call,
+ ObjectProxy::TIMEOUT_USE_DEFAULT,
+ base::Bind(&Property<T>::OnSet,
+ GetWeakPtr(),
+ callback));
+ }
+
+ // Callback for Set(), may be overriden by sub-classes if interfaces
+ // use different response arguments.
+ virtual void OnSet(SetCallback callback, Response* response) {
+ DVLOG(1) << "OnSet: " << name();
+ LOG_IF(WARNING, !response) << name() << ": Set: failed.";
+ if (!callback.is_null())
+ callback.Run(response);
+ }
+
+ // Updates the cached property value by popping from |reader| which
+ // should be positioned at the property value, generally of variant
+ // type. Implementation provided by specialization.
+ virtual bool PopValueFromReader(MessageReader* reader);
+
+ // Appends the passed |value| to |writer|, generally as a variant type.
+ // Implementation provided by specialization.
+ virtual void AppendToWriter(MessageWriter* writer, const T& value);
+
+ protected:
+ // Get a weak pointer to this propertyt, provided so that sub-classes
+ // overriding methods that make D-Bus calls may use the existing (or
+ // override) callbacks without providing their own weak pointer factory.
+ base::WeakPtr<Property<T> > GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ private:
+ // Current cached value of the property.
+ T value_;
+
+ // Weak pointer factory as D-Bus callbacks may last longer than these
+ // objects.
+ base::WeakPtrFactory<Property<T> > weak_ptr_factory_;
+};
+
+} // namespace dbus
+
+#endif // DBUS_PROPERTY_H_
diff --git a/dbus/property_unittest.cc b/dbus/property_unittest.cc
new file mode 100644
index 0000000..effb751
--- /dev/null
+++ b/dbus/property_unittest.cc
@@ -0,0 +1,199 @@
+// Copyright (c) 2012 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/property.h"
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/logging.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 property test exerises the asynchronous APIs in PropertySet and
+// Property<>.
+class PropertyTest : public testing::Test {
+ public:
+ PropertyTest() {
+ }
+
+ 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,
+ PropertyChangedCallback property_changed_callback)
+ : dbus::PropertySet(object_proxy,
+ "org.chromium.TestService",
+ property_changed_callback) {
+ RegisterProperty("Name", &name);
+ RegisterProperty("Version", &version);
+ RegisterProperty("Methods", &methods);
+ RegisterProperty("Objects", &objects);
+ }
+ };
+
+ 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_thread_message_loop_proxy = 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_thread_message_loop_proxy =
+ dbus_thread_->message_loop_proxy();
+ bus_ = new dbus::Bus(bus_options);
+ object_proxy_ = bus_->GetObjectProxy(
+ "org.chromium.TestService",
+ dbus::ObjectPath("/org/chromium/TestObject"));
+ ASSERT_TRUE(bus_->HasDBusThread());
+
+ // Create the properties structure
+ properties_ = new Properties(object_proxy_,
+ base::Bind(&PropertyTest::OnPropertyChanged,
+ base::Unretained(this)));
+ properties_->ConnectSignals();
+ properties_->GetAll();
+ }
+
+ 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();
+ }
+
+ // Generic callback, bind with a string |id| for passing to
+ // WaitForCallback() to ensure the callback for the right method is
+ // waited for.
+ void PropertyCallback(const std::string& id, bool success) {
+ last_callback_ = id;
+ message_loop_.Quit();
+ }
+
+ protected:
+ // Called when a property value is updated.
+ void OnPropertyChanged(const std::string& name) {
+ updated_properties_.push_back(name);
+ message_loop_.Quit();
+ }
+
+ // Wait for the given number of updates.
+ void WaitForUpdates(size_t num_updates) {
+ while (updated_properties_.size() < num_updates)
+ message_loop_.Run();
+ for (size_t i = 0; i < num_updates; ++i)
+ updated_properties_.erase(updated_properties_.begin());
+ }
+
+ // Name, Version, Methods, Objects
+ static const int kExpectedSignalUpdates = 4;
+
+ // Wait for initial values to be set.
+ void WaitForGetAll() {
+ WaitForUpdates(kExpectedSignalUpdates);
+ }
+
+ // Wait for the callback. |id| is the string bound to the callback when
+ // the method call is made that identifies it and distinguishes from any
+ // other; you can set this to whatever you wish.
+ void WaitForCallback(const std::string& id) {
+ while (last_callback_ != id) {
+ message_loop_.Run();
+ }
+ }
+
+ MessageLoop message_loop_;
+ scoped_ptr<base::Thread> dbus_thread_;
+ scoped_refptr<dbus::Bus> bus_;
+ dbus::ObjectProxy* object_proxy_;
+ Properties* properties_;
+ scoped_ptr<dbus::TestService> test_service_;
+ // Properties updated.
+ std::vector<std::string> updated_properties_;
+ // Last callback received.
+ std::string last_callback_;
+};
+
+TEST_F(PropertyTest, InitialValues) {
+ WaitForGetAll();
+
+ EXPECT_EQ(properties_->name.value(), "TestService");
+ EXPECT_EQ(properties_->version.value(), 10);
+
+ 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(PropertyTest, Get) {
+ WaitForGetAll();
+
+ // Ask for the new Version property.
+ properties_->version.Get(base::Bind(&PropertyTest::PropertyCallback,
+ base::Unretained(this),
+ "Get"));
+ WaitForCallback("Get");
+
+ // Make sure we got a property update too.
+ WaitForUpdates(1);
+
+ EXPECT_EQ(properties_->version.value(), 20);
+}
+
+TEST_F(PropertyTest, Set) {
+ WaitForGetAll();
+
+ // Set a new name.
+ properties_->name.Set("NewService",
+ base::Bind(&PropertyTest::PropertyCallback,
+ base::Unretained(this),
+ "Set"));
+ WaitForCallback("Set");
+
+ // TestService sends a property update.
+ WaitForUpdates(1);
+
+ EXPECT_EQ(properties_->name.value(), "NewService");
+}
diff --git a/dbus/test_service.cc b/dbus/test_service.cc
index 6454bb5..23c4bc5 100644
--- a/dbus/test_service.cc
+++ b/dbus/test_service.cc
@@ -11,11 +11,12 @@
#include "dbus/exported_object.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
+#include "dbus/property.h"
namespace dbus {
-// Echo, SlowEcho, AsyncEcho, BrokenMethod.
-const int TestService::kNumMethodsToExport = 4;
+// Echo, SlowEcho, AsyncEcho, BrokenMethod, GetAll, Get, Set.
+const int TestService::kNumMethodsToExport = 7;
TestService::Options::Options() {
}
@@ -165,6 +166,33 @@ void TestService::Run(MessageLoop* message_loop) {
base::Unretained(this)));
++num_methods;
+ exported_object_->ExportMethod(
+ kPropertiesInterface,
+ kPropertiesGetAll,
+ base::Bind(&TestService::GetAllProperties,
+ base::Unretained(this)),
+ base::Bind(&TestService::OnExported,
+ base::Unretained(this)));
+ ++num_methods;
+
+ exported_object_->ExportMethod(
+ kPropertiesInterface,
+ kPropertiesGet,
+ base::Bind(&TestService::GetProperty,
+ base::Unretained(this)),
+ base::Bind(&TestService::OnExported,
+ base::Unretained(this)));
+ ++num_methods;
+
+ exported_object_->ExportMethod(
+ kPropertiesInterface,
+ kPropertiesSet,
+ base::Bind(&TestService::SetProperty,
+ 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) {
@@ -213,4 +241,160 @@ void TestService::BrokenMethod(
response_sender.Run(NULL);
}
+
+void TestService::GetAllProperties(
+ MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ MessageReader reader(method_call);
+ std::string interface;
+ if (!reader.PopString(&interface)) {
+ response_sender.Run(NULL);
+ 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"]>
+ // ]
+
+ Response* response = Response::FromMethodCall(method_call);
+ MessageWriter writer(response);
+
+ 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);
+
+ response_sender.Run(response);
+}
+
+void TestService::GetProperty(
+ MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ MessageReader reader(method_call);
+ std::string interface;
+ if (!reader.PopString(&interface)) {
+ response_sender.Run(NULL);
+ return;
+ }
+
+ std::string name;
+ if (!reader.PopString(&name)) {
+ response_sender.Run(NULL);
+ return;
+ }
+
+ if (name != "Version") {
+ response_sender.Run(NULL);
+ return;
+ }
+
+ Response* response = Response::FromMethodCall(method_call);
+ MessageWriter writer(response);
+
+ writer.AppendVariantOfInt16(20);
+
+ response_sender.Run(response);
+}
+
+void TestService::SetProperty(
+ MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ MessageReader reader(method_call);
+ std::string interface;
+ if (!reader.PopString(&interface)) {
+ response_sender.Run(NULL);
+ return;
+ }
+
+ std::string name;
+ if (!reader.PopString(&name)) {
+ response_sender.Run(NULL);
+ return;
+ }
+
+ if (name != "Name") {
+ response_sender.Run(NULL);
+ return;
+ }
+
+ std::string value;
+ if (!reader.PopVariantOfString(&value)) {
+ response_sender.Run(NULL);
+ return;
+ }
+
+ SendPropertyChangedSignal(value);
+
+ Response* response = Response::FromMethodCall(method_call);
+ response_sender.Run(response);
+}
+
+void TestService::SendPropertyChangedSignal(const std::string& name) {
+ message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&TestService::SendPropertyChangedSignalInternal,
+ base::Unretained(this),
+ name));
+}
+
+void TestService::SendPropertyChangedSignalInternal(const std::string& name) {
+ dbus::Signal signal(kPropertiesInterface, kPropertiesChanged);
+ dbus::MessageWriter writer(&signal);
+ writer.AppendString("org.chromium.TestService");
+
+ MessageWriter array_writer(NULL);
+ MessageWriter dict_entry_writer(NULL);
+
+ writer.OpenArray("{sv}", &array_writer);
+ array_writer.OpenDictEntry(&dict_entry_writer);
+ dict_entry_writer.AppendString("Name");
+ dict_entry_writer.AppendVariantOfString(name);
+ array_writer.CloseContainer(&dict_entry_writer);
+ writer.CloseContainer(&array_writer);
+
+ exported_object_->SendSignal(&signal);
+}
+
} // namespace dbus
diff --git a/dbus/test_service.h b/dbus/test_service.h
index ea3e5ad..2b49229 100644
--- a/dbus/test_service.h
+++ b/dbus/test_service.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 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.
@@ -106,6 +106,24 @@ class TestService : public base::Thread {
void BrokenMethod(MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender);
+ // Returns a set of property values for testing.
+ void GetAllProperties(MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender);
+
+ // Returns a new value of 20 for the Version property when called.
+ void GetProperty(MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender);
+
+ // Allows the name property to be changed, errors otherwise.
+ void SetProperty(MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender);
+
+ // Sends a property changed signal for the name property.
+ void SendPropertyChangedSignal(const std::string& name);
+
+ // Helper function for SendPropertyChangedSignal().
+ void SendPropertyChangedSignalInternal(const std::string& name);
+
scoped_refptr<base::MessageLoopProxy> dbus_thread_message_loop_proxy_;
base::WaitableEvent on_all_methods_exported_;
// The number of methods actually exported.