# Copyright (C) 2015-2016 Red Hat, Inc. All rights reserved. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions # of the GNU General Public License v.2. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import dbus import dbus.service from . import cfg from .utils import get_properties, add_properties, get_object_property_diff, \ log_debug from .state import State # noinspection PyPep8Naming,PyUnresolvedReferences class AutomatedProperties(dbus.service.Object): """ This class implements the needed interfaces for: org.freedesktop.DBus.Properties Other classes inherit from it to get the same behavior """ def __init__(self, object_path, search_method=None): dbus.service.Object.__init__(self, cfg.bus, object_path) self._ap_interface = [] self._ap_o_path = object_path self._ap_search_method = search_method self.state = None def dbus_object_path(self): return self._ap_o_path def emit_data(self): props = {} for i in self.interface(): props[i] = AutomatedProperties._get_all_prop(self, i) return self._ap_o_path, props def set_interface(self, interface): """ With inheritance we can't easily tell what interfaces a class provides so we will have each class that implements an interface tell the base AutomatedProperties what it is they do provide. This is kind of clunky and perhaps we can figure out a better way to do this later. :param interface: An interface the object supports :return: """ if interface not in self._ap_interface: self._ap_interface.append(interface) # noinspection PyUnusedLocal def interface(self, all_interfaces=False): if all_interfaces: cpy = list(self._ap_interface) cpy.extend( ["org.freedesktop.DBus.Introspectable", "org.freedesktop.DBus.Properties"]) return cpy return self._ap_interface @staticmethod def _get_prop(obj, interface_name, property_name): value = getattr(obj, property_name) # Note: If we get an exception in this handler we won't know about it, # only the side effect of no returned value! log_debug('Get (%s), type (%s), value(%s)' % (property_name, str(type(value)), str(value))) return value # Properties # noinspection PyUnusedLocal @dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v', async_callbacks=('cb', 'cbe')) def Get(self, interface_name, property_name, cb, cbe): # Note: If we get an exception in this handler we won't know about it, # only the side effect of no returned value! r = cfg.create_request_entry( -1, AutomatedProperties._get_prop, (self, interface_name, property_name), cb, cbe, False) cfg.worker_q.put(r) @staticmethod def _get_all_prop(obj, interface_name): if interface_name in obj.interface(True): # Using introspection, lets build this dynamically properties = get_properties(obj) if interface_name in properties: return properties[interface_name][1] return {} raise dbus.exceptions.DBusException( obj._ap_interface, 'The object %s does not implement the %s interface' % (obj.__class__, interface_name)) @dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE, in_signature='s', out_signature='a{sv}', async_callbacks=('cb', 'cbe')) def GetAll(self, interface_name, cb, cbe): r = cfg.create_request_entry( -1, AutomatedProperties._get_all_prop, (self, interface_name), cb, cbe, False) cfg.worker_q.put(r) @dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE, in_signature='ssv') def Set(self, interface_name, property_name, new_value): setattr(self, property_name, new_value) self.PropertiesChanged(interface_name, {property_name: new_value}, []) # As dbus-python does not support introspection for properties we will # get the autogenerated xml and then add our wanted properties to it. @dbus.service.method(dbus_interface=dbus.INTROSPECTABLE_IFACE, out_signature='s') def Introspect(self): r = dbus.service.Object.Introspect(self, self._ap_o_path, cfg.bus) # Look at the properties in the class props = get_properties(self) for int_f, v in props.items(): r = add_properties(r, int_f, v[0]) return r @dbus.service.signal(dbus_interface=dbus.PROPERTIES_IFACE, signature='sa{sv}as') def PropertiesChanged(self, interface_name, changed_properties, invalidated_properties): log_debug(('SIGNAL: PropertiesChanged(%s, %s, %s, %s)' % (str(self._ap_o_path), str(interface_name), str(changed_properties), str(invalidated_properties)))) def refresh(self, search_key=None, object_state=None): """ Take the values (properties) of an object and update them with what lvm currently has. You can either fetch the new ones or supply the new state to be updated with :param search_key: The value to use to search for :param object_state: Use this as the new object state """ num_changed = 0 # If we can't do a lookup, bail now, this happens if we blindly walk # through all dbus objects as some don't have a search method, like # 'Manager' object. if not self._ap_search_method: return 0 search = self.lvm_id if search_key: search = search_key # Either we have the new object state or we need to go fetch it if object_state: new_state = object_state else: new_state = self._ap_search_method([search])[0] assert isinstance(new_state, State) assert new_state # When we refresh an object the object identifiers might have changed # because LVM allows the user to change them (name & uuid), thus if # they have changed we need to update the object manager so that # look-ups will happen correctly old_id = self.state.identifiers() new_id = new_state.identifiers() if old_id[0] != new_id[0] or old_id[1] != new_id[1]: cfg.om.lookup_update(self, new_id[0], new_id[1]) # Grab the properties values, then replace the state of the object # and retrieve the new values. o_prop = get_properties(self) self.state = new_state n_prop = get_properties(self) changed = get_object_property_diff(o_prop, n_prop) if changed: for int_f, v in changed.items(): self.PropertiesChanged(int_f, v, []) num_changed += 1 return num_changed