From 7812317d4fcf0b9aaa4b3e7c49e7719bba5d7f1a Mon Sep 17 00:00:00 2001
From: leigh123linux <leigh123linux@googlemail.com>
Date: Wed, 19 Aug 2015 14:23:45 +0100
Subject: [PATCH] Revert "drop object patch which was rejected by upstream
 because it breaks API"

This reverts commit 354154a74dd2f57ec7e2b8fa0709546708628196.
---
 dbus-python.spec     |  13 +-
 object_manager.patch | 473 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 480 insertions(+), 6 deletions(-)
 create mode 100644 object_manager.patch

diff --git a/dbus-python.spec b/dbus-python.spec
index 1865cf8..19fb17f 100644
--- a/dbus-python.spec
+++ b/dbus-python.spec
@@ -1,15 +1,18 @@
 Summary: D-Bus Python Bindings 
 Name:    dbus-python
 Version: 1.2.0
-Release: 9%{?dist}
+Release: 8%{?dist}
 
 License: MIT
 URL:     http://www.freedesktop.org/wiki/Software/DBusBindings/
 Source0: http://dbus.freedesktop.org/releases/dbus-python/%{name}-%{version}.tar.gz
 Source1: http://dbus.freedesktop.org/releases/dbus-python/%{name}-%{version}.tar.gz.asc
 
+# Added functionality for Fedora server dbus api requested by sgallagh
+# https://bugs.freedesktop.org/show_bug.cgi?id=26903#c9
+Patch0:  object_manager.patch
 # borrow centos7 patch to use sitearch properly
-Patch0: 0001-Move-python-modules-to-architecture-specific-directo.patch
+Patch2: 0001-Move-python-modules-to-architecture-specific-directo.patch
 
 BuildRequires: dbus-devel
 BuildRequires: dbus-glib-devel
@@ -42,7 +45,8 @@ Summary: D-Bus bindings for python3
 
 %prep
 %setup -q
-%patch0 -p1 -b .sitearch
+%patch0 -p1 -b .object_manager
+%patch2 -p1 -b .sitearch
 
 # For new arches (aarch64/ppc64le), and patch2
 autoreconf -vif
@@ -94,9 +98,6 @@ make check -k -C python3-build
 
 
 %changelog
-* Wed Aug 19 2015 Leigh Scott <leigh123linux@googlemail.com> - 1.2.0-9
-- drop object patch which was rejected by upstream because it breaks API
-
 * Wed Jun 17 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.2.0-8
 - Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild
 
diff --git a/object_manager.patch b/object_manager.patch
new file mode 100644
index 0000000..df0c53d
--- /dev/null
+++ b/object_manager.patch
@@ -0,0 +1,473 @@
+From b5f097c622ac89df3ccb97931decd45bae3c4bd2 Mon Sep 17 00:00:00 2001
+From: Marko Kohtala <marko.kohtala@gmail.com>
+Date: Tue, 13 May 2014 22:06:07 +0300
+Subject: [PATCH] Implement dbus.service.property decorator and
+ PropertiesInterface
+
+This adds dbus server side support for properties using a decorator.
+The decorator automagically adds PropertiesInterface to the object.
+
+It also simplifies the _dbus_class_table shared by all derived classes
+and containing them to a _dbus_interface_table on each derived class.
+---
+ dbus/decorators.py          |  95 ++++++++++++++++++++++++++
+ dbus/service.py             | 163 ++++++++++++++++++++++++++++++++++++++------
+ examples/example-client.py  |  18 +++--
+ examples/example-service.py |  14 +++-
+ 4 files changed, 261 insertions(+), 29 deletions(-)
+
+diff --git a/dbus/decorators.py b/dbus/decorators.py
+index b164582..d41869a 100644
+--- a/dbus/decorators.py
++++ b/dbus/decorators.py
+@@ -343,3 +343,98 @@ def signal(dbus_interface, signature=None, path_keyword=None,
+         return emit_signal
+ 
+     return decorator
++
++
++class property(object):
++    """A decorator used to mark properties of a `dbus.service.Object`.
++    """
++
++    def __init__(self, dbus_interface=None, signature=None,
++                 property_name=None, emits_changed_signal=None,
++                 fget=None, fset=None, doc=None):
++        """Initialize the decorator used to mark properties of a
++        `dbus.service.Object`.
++
++        :Parameters:
++            `dbus_interface` : str
++                The D-Bus interface owning the property
++
++            `signature` : str
++                The signature of the property in the usual D-Bus notation. The
++                signature must be suitable to be carried in a variant.
++
++            `property_name` : str
++                A name for the property. Defaults to the name of the getter or
++                setter function.
++
++            `emits_changed_signal` : True, False, "invalidates", or None
++                Tells for introspection if the object emits PropertiesChanged
++                signal.
++
++            `fget` : func
++                Getter function taking the instance from which to read the
++                property.
++
++            `fset` : func
++                Setter function taking the instance to which set the property
++                and the property value.
++
++            `doc` : str
++                Documentation string for the property. Defaults to documentation
++                string of getter function.
++
++                :Since: 1.3.0
++        """
++        validate_interface_name(dbus_interface)
++        self._dbus_interface = dbus_interface
++
++        self._init_property_name = property_name
++        if property_name is None:
++            if fget is not None:
++                property_name = fget.__name__
++            elif fset is not None:
++                property_name = fset.__name__
++        if property_name:
++            validate_member_name(property_name)
++        self.__name__ = property_name
++
++        self._init_doc = doc
++        if doc is None and fget is not None:
++            doc = getattr(fget, "__doc__", None)
++        self.fget = fget
++        self.fset = fset
++        self.__doc__ = doc
++
++        self._emits_changed_signal = emits_changed_signal
++        if len(tuple(Signature(signature))) != 1:
++            raise ValueError('signature must have only one item')
++        self._dbus_signature = signature
++
++    def __get__(self, inst, type=None):
++        if inst is None:
++            return self
++        if self.fget is None:
++            raise AttributeError("unreadable attribute")
++        return self.fget(inst)
++
++    def __set__(self, inst, value):
++        if self.fset is None:
++            raise AttributeError("can't set attribute")
++        self.fset(inst, value)
++
++    def __call__(self, fget):
++        return self.getter(fget)
++
++    def _copy(self, fget=None, fset=None):
++        return property(dbus_interface=self._dbus_interface,
++                        signature=self._dbus_signature,
++                        property_name=self._init_property_name,
++                        emits_changed_signal=self._emits_changed_signal,
++                        fget=fget or self.fget, fset=fset or self.fset,
++                        doc=self._init_doc)
++
++    def getter(self, fget):
++        return self._copy(fget=fget)
++
++    def setter(self, fset):
++        return self._copy(fset=fset)
+diff --git a/dbus/service.py b/dbus/service.py
+index b1fc21d..fdb3ee4 100644
+--- a/dbus/service.py
++++ b/dbus/service.py
+@@ -23,7 +23,7 @@
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ # DEALINGS IN THE SOFTWARE.
+ 
+-__all__ = ('BusName', 'Object', 'method', 'signal')
++__all__ = ('BusName', 'Object', 'PropertiesInterface', 'method', 'property', 'signal')
+ __docformat__ = 'restructuredtext'
+ 
+ import sys
+@@ -34,8 +34,10 @@ from collections import Sequence
+ 
+ import _dbus_bindings
+ from dbus import (
+-    INTROSPECTABLE_IFACE, ObjectPath, SessionBus, Signature, Struct,
+-    validate_bus_name, validate_object_path)
++    INTROSPECTABLE_IFACE, ObjectPath, PROPERTIES_IFACE, SessionBus, Signature,
++    Struct, validate_bus_name, validate_object_path)
++_builtin_property = property
++from dbus.decorators import method, signal, property
+ from dbus.decorators import method, signal
+ from dbus.exceptions import (
+     DBusException, NameExistsException, UnknownMethodException)
+@@ -297,20 +299,25 @@ def _method_reply_error(connection, message, exception):
+ 
+ 
+ class InterfaceType(type):
+-    def __init__(cls, name, bases, dct):
+-        # these attributes are shared between all instances of the Interface
+-        # object, so this has to be a dictionary that maps class names to
+-        # the per-class introspection/interface data
+-        class_table = getattr(cls, '_dbus_class_table', {})
+-        cls._dbus_class_table = class_table
+-        interface_table = class_table[cls.__module__ + '.' + name] = {}
++    def __new__(cls, name, bases, dct):
++        # Properties require the PropertiesInterface base.
++        for func in dct.values():
++            if isinstance(func, property):
++                for b in bases:
++                    if issubclass(b, PropertiesInterface):
++                        break
++                else:
++                    bases += (PropertiesInterface,)
++                break
++
++        interface_table = dct.setdefault('_dbus_interface_table', {})
+ 
+         # merge all the name -> method tables for all the interfaces
+         # implemented by our base classes into our own
+         for b in bases:
+-            base_name = b.__module__ + '.' + b.__name__
+-            if getattr(b, '_dbus_class_table', False):
+-                for (interface, method_table) in class_table[base_name].items():
++            base_interface_table = getattr(b, '_dbus_interface_table', False)
++            if base_interface_table:
++                for (interface, method_table) in base_interface_table.items():
+                     our_method_table = interface_table.setdefault(interface, {})
+                     our_method_table.update(method_table)
+ 
+@@ -320,9 +327,9 @@ class InterfaceType(type):
+                 method_table = interface_table.setdefault(func._dbus_interface, {})
+                 method_table[func.__name__] = func
+ 
+-        super(InterfaceType, cls).__init__(name, bases, dct)
++        return type.__new__(cls, name, bases, dct)
+ 
+-    # methods are different to signals, so we have two functions... :)
++    # methods are different to signals and properties, so we have three functions... :)
+     def _reflect_on_method(cls, func):
+         args = func._dbus_args
+ 
+@@ -370,12 +377,107 @@ class InterfaceType(type):
+ 
+         return reflection_data
+ 
++    def _reflect_on_property(cls, descriptor):
++        signature = descriptor._dbus_signature
++        if signature is None:
++            signature = 'v'
++
++        if descriptor.fget:
++            if descriptor.fset:
++                access = "readwrite"
++            else:
++                access = "read"
++        elif descriptor.fset:
++            access = "write"
++        else:
++            return ""
++        reflection_data = '    <property access="%s" type="%s" name="%s"' % (access, signature, descriptor.__name__)
++        if descriptor._emits_changed_signal is not None:
++            value = {True: "true", False: "false", "invalidates": "invalidates"}[descriptor._emits_changed_signal]
++            reflection_data += '>\n      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="%s"/>\n    </property>\n' % (value,)
++        else:
++            reflection_data += ' />\n'
++        return reflection_data
++
+ 
+ # Define Interface as an instance of the metaclass InterfaceType, in a way
+ # that is compatible across both Python 2 and Python 3.
+ Interface = InterfaceType('Interface', (object,), {})
+ 
+ 
++class PropertiesInterface(Interface):
++    """An object with properties must inherit from this interface."""
++
++    def _get_decorator(self, interface_name, property_name):
++        interfaces = self._dbus_interface_table
++        if interface_name:
++            interface = interfaces.get(interface_name)
++            if interface is None:
++                raise DBusException("No interface %s on object" % interface_name)
++            prop = interface.get(property_name)
++            if prop is None:
++                raise DBusException("No property %s on object interface %s" % (property_name, interface_name))
++            if not isinstance(prop, property):
++                raise DBusException("Name %s on object interface %s is not a property" % (property_name, interface_name))
++            return prop
++        else:
++            for interface in interfaces.itervalues():
++                prop = interface.get(property_name)
++                if prop and isinstance(prop, property):
++                    return prop
++            raise DBusException("No property %s found" % (property_name,))
++
++    @method(PROPERTIES_IFACE, in_signature="ss", out_signature="v")
++    def Get(self, interface_name, property_name):
++        """Get the value of the property on named interface. interface_name
++        may be empty, but if there are many properties with the same name the
++        behaviour is undefined.
++        """
++        prop = self._get_decorator(interface_name, property_name)
++        if not prop.fget:
++            raise DBusException("Property %s not readable" % property_name)
++        return prop.fget(self)
++
++    @method(PROPERTIES_IFACE, in_signature="ssv")
++    def Set(self, interface_name, property_name, value):
++        """Set value of property on named interface to value. interface_name
++        may be empty, but if there are many properties with the same name the
++        behaviour is undefined.
++        """
++        prop = self._get_decorator(interface_name, property_name)
++        if not prop.fset:
++            raise DBusException("Property %s not writable" % property_name)
++        return prop.fset(self, value)
++
++    @method(PROPERTIES_IFACE, in_signature="s", out_signature="a{sv}")
++    def GetAll(self, interface_name):
++        """Return a dictionary of all property names and values. Returns only
++        readable properties.
++        """
++        interfaces = self._dbus_interface_table
++        if interface_name:
++            iface = interfaces.get(interface_name)
++            if iface is None:
++                raise DBusException("No interface %s on object" % interface_name)
++            ifaces = [iface]
++        else:
++            ifaces = interfaces.values()
++        properties = {}
++        for iface in ifaces:
++            for name, prop in iface.items():
++                if not isinstance(prop, property):
++                    continue
++                if not prop.fget or name in properties:
++                    continue
++                properties[name] = prop.fget(self)
++        return properties
++
++    @signal(PROPERTIES_IFACE, signature='sa{sv}as')
++    def PropertiesChanged(self, interface_name, changed_properties,
++                          invalidated_properties):
++        pass
++
++
+ #: A unique object used as the value of Object._object_path and
+ #: Object._connection if it's actually in more than one place
+ _MANY = object()
+@@ -384,11 +486,12 @@ class Object(Interface):
+     r"""A base class for exporting your own Objects across the Bus.
+ 
+     Just inherit from Object and mark exported methods with the
+-    @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
++    @\ `dbus.service.method`, @\ `dbus.service.signal` or
++    @\ `dbus.service.property` decorator.
+ 
+     Example::
+ 
+-        class Example(dbus.service.object):
++        class Example(dbus.service.Object):
+             def __init__(self, object_path):
+                 dbus.service.Object.__init__(self, dbus.SessionBus(), path)
+                 self._last_input = None
+@@ -397,6 +500,8 @@ class Object(Interface):
+                                  in_signature='v', out_signature='s')
+             def StringifyVariant(self, var):
+                 self.LastInputChanged(var)      # emits the signal
++                # Emit the property changed signal
++                self.PropertiesChanged('com.example.Sample', {'LastInput': var}, [])
+                 return str(var)
+ 
+             @dbus.service.signal(interface='com.example.Sample',
+@@ -410,6 +515,20 @@ class Object(Interface):
+                                  in_signature='', out_signature='v')
+             def GetLastInput(self):
+                 return self._last_input
++
++            @dbus.service.property(interface='com.example.Sample',
++                                   signature='s')
++            def LastInput(self):
++                return self._last_input
++
++            @LastInput.setter
++            def LastInput(self, value):
++                self._last_input = value
++                # By default a property is expected to send the
++                # PropertiesChanged signal when value changes.
++                self.PropertiesChanged('com.example.Sample',
++                                       {'LastInput': var}, [])
++
+     """
+ 
+     #: If True, this object can be made available at more than one object path.
+@@ -484,7 +603,7 @@ class Object(Interface):
+         if conn is not None and object_path is not None:
+             self.add_to_connection(conn, object_path)
+ 
+-    @property
++    @_builtin_property
+     def __dbus_object_path__(self):
+         """The object-path at which this object is available.
+         Access raises AttributeError if there is no object path, or more than
+@@ -500,7 +619,7 @@ class Object(Interface):
+         else:
+             return self._object_path
+ 
+-    @property
++    @_builtin_property
+     def connection(self):
+         """The Connection on which this object is available.
+         Access raises AttributeError if there is no Connection, or more than
+@@ -516,7 +635,7 @@ class Object(Interface):
+         else:
+             return self._connection
+ 
+-    @property
++    @_builtin_property
+     def locations(self):
+         """An iterable over tuples representing locations at which this
+         object is available.
+@@ -762,7 +881,7 @@ class Object(Interface):
+         reflection_data = _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+         reflection_data += '<node name="%s">\n' % object_path
+ 
+-        interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
++        interfaces = self._dbus_interface_table
+         for (name, funcs) in interfaces.items():
+             reflection_data += '  <interface name="%s">\n' % (name)
+ 
+@@ -771,6 +890,8 @@ class Object(Interface):
+                     reflection_data += self.__class__._reflect_on_method(func)
+                 elif getattr(func, '_dbus_is_signal', False):
+                     reflection_data += self.__class__._reflect_on_signal(func)
++                elif isinstance(func, property):
++                    reflection_data += self.__class__._reflect_on_property(func)
+ 
+             reflection_data += '  </interface>\n'
+ 
+diff --git a/examples/example-client.py b/examples/example-client.py
+index 262f892..b8fdc17 100644
+--- a/examples/example-client.py
++++ b/examples/example-client.py
+@@ -46,30 +46,36 @@ def main():
+             dbus_interface = "com.example.SampleInterface")
+     except dbus.DBusException:
+         print_exc()
+-        print usage
++        print(usage)
+         sys.exit(1)
+ 
+-    print (hello_reply_list)
++    print(hello_reply_list)
+ 
+     # ... or create an Interface wrapper for the remote object
+     iface = dbus.Interface(remote_object, "com.example.SampleInterface")
+ 
+     hello_reply_tuple = iface.GetTuple()
+ 
+-    print hello_reply_tuple
++    print(hello_reply_tuple)
+ 
+     hello_reply_dict = iface.GetDict()
+ 
+-    print hello_reply_dict
++    print(hello_reply_dict)
+ 
+     # D-Bus exceptions are mapped to Python exceptions
+     try:
+         iface.RaiseException()
+     except dbus.DBusException as e:
+-        print str(e)
++        print(str(e))
++
++    # D-Bus properties are implemented on server, but client needs to access
++    # them directly.
++    properties = dbus.Interface(remote_object, dbus.PROPERTIES_IFACE)
++    property_value = properties.Get("com.example.SampleInterface", "Property")
++    print(property_value)
+ 
+     # introspection is automatically supported
+-    print remote_object.Introspect(dbus_interface="org.freedesktop.DBus.Introspectable")
++    print(remote_object.Introspect(dbus_interface="org.freedesktop.DBus.Introspectable"))
+ 
+     if sys.argv[1:] == ['--exit-service']:
+         iface.Exit()
+diff --git a/examples/example-service.py b/examples/example-service.py
+index c42b526..5da98d5 100644
+--- a/examples/example-service.py
++++ b/examples/example-service.py
+@@ -69,6 +69,16 @@ class SomeObject(dbus.service.Object):
+     def Exit(self):
+         mainloop.quit()
+ 
++    _property_default = "Hello world!"
++    @dbus.service.property("com.example.SampleInterface",
++                           signature='s')
++    def Property(self):
++        """Sample property"""
++        return self._property_default
++
++    @Property.setter
++    def Property(self, value):
++        self._property_default = value
+ 
+ if __name__ == '__main__':
+     dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+@@ -78,6 +88,6 @@ if __name__ == '__main__':
+     object = SomeObject(session_bus, '/SomeObject')
+ 
+     mainloop = gobject.MainLoop()
+-    print "Running example service."
+-    print usage
++    print("Running example service.")
++    print(usage)
+     mainloop.run()
+-- 
+1.8.4.5
+