1# Lint as: python2, python3 2# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6""" 7Python implementation of the standard interfaces: 8 - org.freedesktop.DBus.Properties 9 - org.freedesktop.DBus.Introspectable (TODO(armansito): May not be necessary) 10 - org.freedesktop.DBus.ObjectManager 11 12""" 13 14import dbus 15import dbus.service 16import dbus.types 17import logging 18 19from autotest_lib.client.cros.cellular import mm1_constants 20from autotest_lib.client.cros.cellular.pseudomodem import pm_errors 21from autotest_lib.client.cros.cellular.pseudomodem import utils 22 23class MMPropertyError(pm_errors.MMError): 24 """ 25 MMPropertyError is raised by DBusProperties methods 26 to indicate that a value for the given interface or 27 property could not be found. 28 29 """ 30 31 UNKNOWN_PROPERTY = 0 32 UNKNOWN_INTERFACE = 1 33 34 def __init__(self, errno, *args, **kwargs): 35 super(MMPropertyError, self).__init__(errno, args, kwargs) 36 37 38 def _Setup(self): 39 self._error_name_base = mm1_constants.I_MODEM_MANAGER 40 self._error_name_map = { 41 self.UNKNOWN_PROPERTY : '.UnknownProperty', 42 self.UNKNOWN_INTERFACE : '.UnknownInterface' 43 } 44 45 46class DBusProperties(dbus.service.Object): 47 """ 48 == org.freedesktop.DBus.Properties == 49 50 This serves as the abstract base class for all objects that expose 51 properties. Each instance holds a mapping from DBus interface names to 52 property-value mappings, which are provided by the subclasses. 53 54 """ 55 56 def __init__(self, path, bus=None, config=None): 57 """ 58 @param bus: The pydbus bus object. 59 @param path: The DBus object path of this object. 60 @param config: This is an optional dictionary that can be used to 61 initialize the property dictionary with values other than the 62 ones provided by |_InitializeProperties|. The dictionary has to 63 contain a mapping from DBus interfaces to property-value pairs, 64 and all contained keys must have been initialized during 65 |_InitializeProperties|, i.e. if config contains any keys that 66 have not been already set in the internal property dictionary, 67 an error will be raised (see DBusProperties.Set). 68 69 """ 70 if not path: 71 raise TypeError(('A value for "path" has to be provided that is ' 72 'not "None".')) 73 if bus: 74 dbus.service.Object.__init__(self, bus, path) 75 else: 76 dbus.service.Object.__init__(self, None, None) 77 self.path = path 78 self.bus = bus 79 self._properties = self._InitializeProperties() 80 81 if config: 82 for key, props in config: 83 for prop, val in props: 84 self.Set(key, prop, val) 85 86 87 @property 88 def properties(self): 89 """ 90 @returns: The property dictionary. 91 92 """ 93 return self._properties 94 95 96 def SetBus(self, bus): 97 """ 98 Sets the pydbus bus object that this instance of DBusProperties should 99 be exposed on. Call this method only if |bus| is not already set. 100 101 @param bus: The pydbus bus object to assign. 102 103 """ 104 self.bus = bus 105 self.add_to_connection(bus, self.path) 106 107 108 def SetPath(self, path): 109 """ 110 Exposes this object on a new DBus path. This method fails with an 111 Exception by default, since exposing an object on multiple paths is 112 disallowed by default. 113 114 Subclasses can change this behavior by setting the 115 SUPPORTS_MULTIPLE_OBJECT_PATHS class variable to True. 116 117 @param path: The new path to assign to this object. 118 119 """ 120 self.path = path 121 self.add_to_connection(self.bus, path) 122 123 124 def SetUInt32(self, interface_name, property_name, value): 125 """ 126 Sets the given uint32 value matching the given property and interface. 127 Wraps the given value inside a dbus.types.UInt32. 128 129 @param interface_name: The DBus interface name. 130 @param property_name: The property name. 131 @param value: Value to set. 132 @raises: MMPropertyError, if the given |interface_name| or 133 |property_name| is not exposed by this object. 134 Emits: 135 PropertiesChanged 136 137 """ 138 self.Set(interface_name, property_name, dbus.types.UInt32(value)) 139 140 141 def SetInt32(self, interface_name, property_name, value): 142 """ 143 Sets the given int32 value matching the given property and interface. 144 Wraps the given value inside a dbus.types.Int32. 145 146 @param interface_name: The DBus interface name. 147 @param property_name: The property name. 148 @param value: Value to set. 149 @raises: MMPropertyError, if the given |interface_name| or 150 |property_name| is not exposed by this object. 151 Emits: 152 PropertiesChanged 153 154 """ 155 self.Set(interface_name, property_name, dbus.types.Int32(value)) 156 157 158 @utils.log_dbus_method() 159 @dbus.service.method(mm1_constants.I_PROPERTIES, in_signature='ss', 160 out_signature='v') 161 def Get(self, interface_name, property_name): 162 """ 163 Returns the value matching the given property and interface. 164 165 @param interface_name: The DBus interface name. 166 @param property_name: The property name. 167 @returns: The value matching the given property and interface. 168 @raises: MMPropertyError, if the given |interface_name| or 169 |property_name| is not exposed by this object. 170 171 """ 172 logging.info( 173 '%s: Get(%s, %s)', 174 self.path, 175 interface_name, 176 property_name) 177 val = self.GetAll(interface_name).get(property_name, None) 178 if val is None: 179 message = ("Property '%s' not implemented for interface '%s'." % 180 (property_name, interface_name)) 181 logging.info(message) 182 raise MMPropertyError( 183 MMPropertyError.UNKNOWN_PROPERTY, message) 184 return val 185 186 187 @utils.log_dbus_method() 188 @dbus.service.method(mm1_constants.I_PROPERTIES, in_signature='ssv') 189 def Set(self, interface_name, property_name, value): 190 """ 191 Sets the value matching the given property and interface. 192 193 @param interface_name: The DBus interface name. 194 @param property_name: The property name. 195 @param value: The value to set. 196 @raises: MMPropertyError, if the given |interface_name| or 197 |property_name| is not exposed by this object. 198 Emits: 199 PropertiesChanged 200 201 """ 202 logging.info( 203 '%s: Set(%s, %s)', 204 self.path, 205 interface_name, 206 property_name) 207 props = self.GetAll(interface_name) 208 if property_name not in props: 209 raise MMPropertyError( 210 MMPropertyError.UNKNOWN_PROPERTY, 211 ("Property '%s' not implemented for " 212 "interface '%s'.") % 213 (property_name, interface_name)) 214 if props[property_name] == value: 215 logging.info("Property '%s' already has value '%s'. Ignoring.", 216 property_name, 217 value) 218 return 219 props[property_name] = value 220 changed = { property_name : value } 221 inv = self._InvalidatedPropertiesForChangedValues(changed) 222 self.PropertiesChanged(interface_name, changed, inv) 223 224 225 @utils.log_dbus_method() 226 @dbus.service.method(mm1_constants.I_PROPERTIES, 227 in_signature='s', out_signature='a{sv}') 228 def GetAll(self, interface_name): 229 """ 230 Returns all property-value pairs that match the given interface. 231 232 @param interface_name: The DBus interface name. 233 @returns: A dictionary, containing the properties of the given DBus 234 interface and their values. 235 @raises: MMPropertyError, if the given |interface_name| or 236 |property_name| is not exposed by this object. 237 238 """ 239 logging.info( 240 '%s: GetAll(%s)', 241 self.path, 242 interface_name) 243 props = self._properties.get(interface_name, None) 244 if props is None: 245 raise MMPropertyError( 246 MMPropertyError.UNKNOWN_INTERFACE, 247 "Object does not implement interface '%s'." % 248 interface_name) 249 return props 250 251 252 @dbus.service.signal(mm1_constants.I_PROPERTIES, signature='sa{sv}as') 253 def PropertiesChanged( 254 self, 255 interface_name, 256 changed_properties, 257 invalidated_properties): 258 """ 259 This signal is emitted by Set, when the value of a property is changed. 260 261 @param interface_name: The interface the changed properties belong to. 262 @param changed_properties: Dictionary containing the changed properties 263 and their new values. 264 @param invalidated_properties: List of properties that were invalidated 265 when properties changed. 266 267 """ 268 logging.info(('Properties Changed on interface: %s Changed Properties:' 269 ' %s InvalidatedProperties: %s.', interface_name, 270 str(changed_properties), str(invalidated_properties))) 271 272 273 def SetAll(self, interface, properties): 274 """ 275 Sets the entire property dictionary for the given interface. 276 277 @param interface: String specifying the DBus interface. 278 @param properties: Dictionary containing the properties to set. 279 Emits: 280 PropertiesChanged 281 282 """ 283 old_props = self._properties.get(interface, None) 284 if old_props: 285 invalidated = list(old_props.keys()) 286 else: 287 invalidated = [] 288 self._properties[interface] = properties 289 self.PropertiesChanged(interface, properties, invalidated) 290 291 292 def GetInterfacesAndProperties(self): 293 """ 294 Returns all DBus properties of this object. 295 296 @returns: The complete property dictionary. The returned dict is a tree, 297 where the keys are DBus interfaces and the values are 298 dictionaries that map properties to values. 299 """ 300 return self._properties 301 302 303 def _InvalidatedPropertiesForChangedValues(self, changed): 304 """ 305 Called by Set, returns the list of property names that should become 306 invalidated given the properties and their new values contained in 307 changed. Subclasses can override this method; the default implementation 308 returns an empty list. 309 310 """ 311 return [] 312 313 314 def _InitializeProperties(self): 315 """ 316 Called at instantiation. Subclasses have to override this method and 317 return a dictionary containing mappings from implemented interfaces to 318 dictionaries of property-value mappings. 319 320 """ 321 raise NotImplementedError() 322 323 324class DBusObjectManager(dbus.service.Object): 325 """ 326 == org.freedesktop.DBus.ObjectManager == 327 328 This interface, included in rev. 0.17 of the DBus specification, allows a 329 generic way to control the addition and removal of Modem objects, as well 330 as the addition and removal of interfaces in the given objects. 331 332 """ 333 334 def __init__(self, bus, path): 335 dbus.service.Object.__init__(self, bus, path) 336 self.devices = [] 337 self.bus = bus 338 self.path = path 339 340 341 def Add(self, device): 342 """ 343 Adds a device to the list of devices that are managed by this modem 344 manager. 345 346 @param device: Device to add. 347 Emits: 348 InterfacesAdded 349 350 """ 351 self.devices.append(device) 352 device.manager = self 353 self.InterfacesAdded(device.path, device.GetInterfacesAndProperties()) 354 355 356 def Remove(self, device): 357 """ 358 Removes a device from the list of devices that are managed by this 359 modem manager. 360 361 @param device: Device to remove. 362 Emits: 363 InterfacesRemoved 364 365 """ 366 if device in self.devices: 367 self.devices.remove(device) 368 interfaces = list(device.GetInterfacesAndProperties().keys()) 369 self.InterfacesRemoved(device.path, interfaces) 370 device.remove_from_connection() 371 372 373 @utils.log_dbus_method() 374 @dbus.service.method(mm1_constants.I_OBJECT_MANAGER, 375 out_signature='a{oa{sa{sv}}}') 376 def GetManagedObjects(self): 377 """ 378 @returns: A dictionary containing all objects and their properties. The 379 keys to the dictionary are object paths which are mapped to 380 dictionaries containing mappings from DBus interface names to 381 property-value pairs. 382 383 """ 384 results = {} 385 for device in self.devices: 386 results[dbus.types.ObjectPath(device.path)] = ( 387 device.GetInterfacesAndProperties()) 388 logging.info('%s: GetManagedObjects: %s', self.path, 389 ', '.join(list(results.keys()))) 390 return results 391 392 393 @dbus.service.signal(mm1_constants.I_OBJECT_MANAGER, 394 signature='oa{sa{sv}}') 395 def InterfacesAdded(self, object_path, interfaces_and_properties): 396 """ 397 The InterfacesAdded signal is emitted when either a new object is added 398 or when an existing object gains one or more interfaces. 399 400 @param object_path: Path of the added object. 401 @param interfaces_and_properties: The complete property dictionary that 402 belongs to the recently added object. 403 404 """ 405 logging.info((self.path + ': InterfacesAdded(' + object_path + 406 ', ' + str(interfaces_and_properties)) + ')') 407 408 409 @dbus.service.signal(mm1_constants.I_OBJECT_MANAGER, signature='oas') 410 def InterfacesRemoved(self, object_path, interfaces): 411 """ 412 The InterfacesRemoved signal is emitted whenever an object is removed 413 or it loses one or more interfaces. 414 415 @param object_path: Path of the remove object. 416 @param interfaces_and_properties: The complete property dictionary that 417 belongs to the recently removed object. 418 419 """ 420 logging.info((self.path + ': InterfacesRemoved(' + object_path + 421 ', ' + str(interfaces) + ')')) 422