blob: 847b4bb3d16bc213ede8bd67a688890d45396536 [file] [log] [blame]
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Python implementation of the standard interfaces:
- org.freedesktop.DBus.Properties
- org.freedesktop.DBus.Introspectable (TODO(armansito): May not be necessary)
- org.freedesktop.DBus.ObjectManager
"""
import dbus
import dbus.service
import dbus.types
import logging
import pm_errors
import utils
from autotest_lib.client.cros.cellular import mm1_constants
class MMPropertyError(pm_errors.MMError):
"""
MMPropertyError is raised by DBusProperties methods
to indicate that a value for the given interface or
property could not be found.
"""
UNKNOWN_PROPERTY = 0
UNKNOWN_INTERFACE = 1
def __init__(self, errno, *args, **kwargs):
super(MMPropertyError, self).__init__(errno, args, kwargs)
def _Setup(self):
self._error_name_base = mm1_constants.I_MODEM_MANAGER
self._error_name_map = {
self.UNKNOWN_PROPERTY : '.UnknownProperty',
self.UNKNOWN_INTERFACE : '.UnknownInterface'
}
class DBusProperties(dbus.service.Object):
"""
== org.freedesktop.DBus.Properties ==
This serves as the abstract base class for all objects that expose
properties. Each instance holds a mapping from DBus interface names to
property-value mappings, which are provided by the subclasses.
"""
def __init__(self, path, bus=None, config=None):
"""
@param bus: The pydbus bus object.
@param path: The DBus object path of this object.
@param config: This is an optional dictionary that can be used to
initialize the property dictionary with values other than the
ones provided by |_InitializeProperties|. The dictionary has to
contain a mapping from DBus interfaces to property-value pairs,
and all contained keys must have been initialized during
|_InitializeProperties|, i.e. if config contains any keys that
have not been already set in the internal property dictionary,
an error will be raised (see DBusProperties.Set).
"""
if not path:
raise TypeError(('A value for "path" has to be provided that is '
'not "None".'))
if bus:
dbus.service.Object.__init__(self, bus, path)
else:
dbus.service.Object.__init__(self, None, None)
self.path = path
self.bus = bus
self._properties = self._InitializeProperties()
if config:
for key, props in config:
for prop, val in props:
self.Set(key, prop, val)
@property
def properties(self):
"""
@returns: The property dictionary.
"""
return self._properties
def SetBus(self, bus):
"""
Sets the pydbus bus object that this instance of DBusProperties should
be exposed on. Call this method only if |bus| is not already set.
@param bus: The pydbus bus object to assign.
"""
self.bus = bus
self.add_to_connection(bus, self.path)
def SetPath(self, path):
"""
Exposes this object on a new DBus path. This method fails with an
Exception by default, since exposing an object on multiple paths is
disallowed by default.
Subclasses can change this behavior by setting the
SUPPORTS_MULTIPLE_OBJECT_PATHS class variable to True.
@param path: The new path to assign to this object.
"""
self.path = path
self.add_to_connection(self.bus, path)
def SetUInt32(self, interface_name, property_name, value):
"""
Sets the given uint32 value matching the given property and interface.
Wraps the given value inside a dbus.types.UInt32.
@param interface_name: The DBus interface name.
@param property_name: The property name.
@param value: Value to set.
@raises: MMPropertyError, if the given |interface_name| or
|property_name| is not exposed by this object.
Emits:
PropertiesChanged
"""
self.Set(interface_name, property_name, dbus.types.UInt32(value))
def SetInt32(self, interface_name, property_name, value):
"""
Sets the given int32 value matching the given property and interface.
Wraps the given value inside a dbus.types.Int32.
@param interface_name: The DBus interface name.
@param property_name: The property name.
@param value: Value to set.
@raises: MMPropertyError, if the given |interface_name| or
|property_name| is not exposed by this object.
Emits:
PropertiesChanged
"""
self.Set(interface_name, property_name, dbus.types.Int32(value))
@utils.log_dbus_method()
@dbus.service.method(mm1_constants.I_PROPERTIES, in_signature='ss',
out_signature='v')
def Get(self, interface_name, property_name):
"""
Returns the value matching the given property and interface.
@param interface_name: The DBus interface name.
@param property_name: The property name.
@returns: The value matching the given property and interface.
@raises: MMPropertyError, if the given |interface_name| or
|property_name| is not exposed by this object.
"""
logging.info(
'%s: Get(%s, %s)',
self.path,
interface_name,
property_name)
val = self.GetAll(interface_name).get(property_name, None)
if val is None:
message = ("Property '%s' not implemented for interface '%s'." %
(property_name, interface_name))
logging.info(message)
raise MMPropertyError(
MMPropertyError.UNKNOWN_PROPERTY, message)
return val
@utils.log_dbus_method()
@dbus.service.method(mm1_constants.I_PROPERTIES, in_signature='ssv')
def Set(self, interface_name, property_name, value):
"""
Sets the value matching the given property and interface.
@param interface_name: The DBus interface name.
@param property_name: The property name.
@param value: The value to set.
@raises: MMPropertyError, if the given |interface_name| or
|property_name| is not exposed by this object.
Emits:
PropertiesChanged
"""
logging.info(
'%s: Set(%s, %s)',
self.path,
interface_name,
property_name)
props = self.GetAll(interface_name)
if property_name not in props:
raise MMPropertyError(
MMPropertyError.UNKNOWN_PROPERTY,
("Property '%s' not implemented for "
"interface '%s'.") %
(property_name, interface_name))
if props[property_name] == value:
logging.info("Property '%s' already has value '%s'. Ignoring.",
property_name,
value)
return
props[property_name] = value
changed = { property_name : value }
inv = self._InvalidatedPropertiesForChangedValues(changed)
self.PropertiesChanged(interface_name, changed, inv)
@utils.log_dbus_method()
@dbus.service.method(mm1_constants.I_PROPERTIES,
in_signature='s', out_signature='a{sv}')
def GetAll(self, interface_name):
"""
Returns all property-value pairs that match the given interface.
@param interface_name: The DBus interface name.
@returns: A dictionary, containing the properties of the given DBus
interface and their values.
@raises: MMPropertyError, if the given |interface_name| or
|property_name| is not exposed by this object.
"""
logging.info(
'%s: GetAll(%s)',
self.path,
interface_name)
props = self._properties.get(interface_name, None)
if props is None:
raise MMPropertyError(
MMPropertyError.UNKNOWN_INTERFACE,
"Object does not implement interface '%s'." %
interface_name)
return props
@dbus.service.signal(mm1_constants.I_PROPERTIES, signature='sa{sv}as')
def PropertiesChanged(
self,
interface_name,
changed_properties,
invalidated_properties):
"""
This signal is emitted by Set, when the value of a property is changed.
@param interface_name: The interface the changed properties belong to.
@param changed_properties: Dictionary containing the changed properties
and their new values.
@param invalidated_properties: List of properties that were invalidated
when properties changed.
"""
logging.info(('Properties Changed on interface: %s Changed Properties:'
' %s InvalidatedProperties: %s.', interface_name,
str(changed_properties), str(invalidated_properties)))
def SetAll(self, interface, properties):
"""
Sets the entire property dictionary for the given interface.
@param interface: String specifying the DBus interface.
@param properties: Dictionary containing the properties to set.
Emits:
PropertiesChanged
"""
old_props = self._properties.get(interface, None)
if old_props:
invalidated = old_props.keys()
else:
invalidated = []
self._properties[interface] = properties
self.PropertiesChanged(interface, properties, invalidated)
def GetInterfacesAndProperties(self):
"""
Returns all DBus properties of this object.
@returns: The complete property dictionary. The returned dict is a tree,
where the keys are DBus interfaces and the values are
dictionaries that map properties to values.
"""
return self._properties
def _InvalidatedPropertiesForChangedValues(self, changed):
"""
Called by Set, returns the list of property names that should become
invalidated given the properties and their new values contained in
changed. Subclasses can override this method; the default implementation
returns an empty list.
"""
return []
def _InitializeProperties(self):
"""
Called at instantiation. Subclasses have to override this method and
return a dictionary containing mappings from implemented interfaces to
dictionaries of property-value mappings.
"""
raise NotImplementedError()
class DBusObjectManager(dbus.service.Object):
"""
== org.freedesktop.DBus.ObjectManager ==
This interface, included in rev. 0.17 of the DBus specification, allows a
generic way to control the addition and removal of Modem objects, as well
as the addition and removal of interfaces in the given objects.
"""
def __init__(self, bus, path):
dbus.service.Object.__init__(self, bus, path)
self.devices = []
self.bus = bus
self.path = path
def Add(self, device):
"""
Adds a device to the list of devices that are managed by this modem
manager.
@param device: Device to add.
Emits:
InterfacesAdded
"""
self.devices.append(device)
device.manager = self
self.InterfacesAdded(device.path, device.GetInterfacesAndProperties())
def Remove(self, device):
"""
Removes a device from the list of devices that are managed by this
modem manager.
@param device: Device to remove.
Emits:
InterfacesRemoved
"""
if device in self.devices:
self.devices.remove(device)
interfaces = device.GetInterfacesAndProperties().keys()
self.InterfacesRemoved(device.path, interfaces)
device.remove_from_connection()
@utils.log_dbus_method()
@dbus.service.method(mm1_constants.I_OBJECT_MANAGER,
out_signature='a{oa{sa{sv}}}')
def GetManagedObjects(self):
"""
@returns: A dictionary containing all objects and their properties. The
keys to the dictionary are object paths which are mapped to
dictionaries containing mappings from DBus interface names to
property-value pairs.
"""
results = {}
for device in self.devices:
results[dbus.types.ObjectPath(device.path)] = (
device.GetInterfacesAndProperties())
logging.info('%s: GetManagedObjects: %s', self.path,
', '.join(results.keys()))
return results
@dbus.service.signal(mm1_constants.I_OBJECT_MANAGER,
signature='oa{sa{sv}}')
def InterfacesAdded(self, object_path, interfaces_and_properties):
"""
The InterfacesAdded signal is emitted when either a new object is added
or when an existing object gains one or more interfaces.
@param object_path: Path of the added object.
@param interfaces_and_properties: The complete property dictionary that
belongs to the recently added object.
"""
logging.info((self.path + ': InterfacesAdded(' + object_path +
', ' + str(interfaces_and_properties)) + ')')
@dbus.service.signal(mm1_constants.I_OBJECT_MANAGER, signature='oas')
def InterfacesRemoved(self, object_path, interfaces):
"""
The InterfacesRemoved signal is emitted whenever an object is removed
or it loses one or more interfaces.
@param object_path: Path of the remove object.
@param interfaces_and_properties: The complete property dictionary that
belongs to the recently removed object.
"""
logging.info((self.path + ': InterfacesRemoved(' + object_path +
', ' + str(interfaces) + ')'))