diff options
author | keybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-15 04:21:08 +0000 |
---|---|---|
committer | keybuk@chromium.org <keybuk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-15 04:21:08 +0000 |
commit | cf910da2d3cb23ac300968212dc8a196a64b2726 (patch) | |
tree | b7975c349ab6aeccee275e630b2ece900000d387 | |
parent | c401049c2ed235c156292b632f43198c44641b65 (diff) | |
download | chromium_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
-rw-r--r-- | dbus/dbus.gyp | 3 | ||||
-rw-r--r-- | dbus/property.cc | 425 | ||||
-rw-r--r-- | dbus/property.h | 421 | ||||
-rw-r--r-- | dbus/property_unittest.cc | 199 | ||||
-rw-r--r-- | dbus/test_service.cc | 188 | ||||
-rw-r--r-- | dbus/test_service.h | 20 |
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. |