/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: teddy at recompile
  • Date: 2012-02-26 11:47:19 UTC
  • mto: This revision was merged to the branch mainline in revision 561.
  • Revision ID: teddy@recompile.se-20120226114719-m3odq4s34ebt6hg8
Add facilities for D-Bus interface annotations and use them to mark
the old D-Bus interfaces as deprecated.

* mandos (dbus_interface_annotations): New decorator.
  (DBusObjectWithProperties._is_dbus_property): Removed.  All callers
                                                changed.
  (DBusObjectWithProperties._is_dbus_thing): New; generalized version
                                             of "_is_dbus_property".
  (DBusObjectWithProperties._get_all_dbus_properties): Removed.  All
                                                       callers
                                                       changed.
  (DBusObjectWithProperties._get_all_dbus_things): New; generalized
                                                   version of
                                                   "_get_all_dbus_properties".
  (DBusObjectWithProperties.Introspect): Also add interface
                                         annotation tags.
  (AlternateDBusNamesMetaclass.__new__): Also copy interface
                                         annotations.  Add
                                         "deprecated" annotation on
                                         old interface.
  (ClientDBus._foo): New interface annotation to mark non-compliance
                     with the standard property change signal
                     interface.
  (MandosDBusService): Inherit from DBusObjectWithProperties to get
                       interface annotation support.
  (MandosDBusService._foo): New interface annotation to mark
                            non-compliance with the standard property
                            change signal interface.

Show diffs side-by-side

added added

removed removed

Lines of Context:
65
65
import types
66
66
import binascii
67
67
import tempfile
 
68
import itertools
68
69
 
69
70
import dbus
70
71
import dbus.service
771
772
    return decorator
772
773
 
773
774
 
 
775
def dbus_interface_annotations(dbus_interface):
 
776
    """Decorator for marking functions returning interface annotations.
 
777
    
 
778
    Usage:
 
779
    
 
780
    @dbus_interface_annotations("org.example.Interface")
 
781
    def _foo(self):  # Function name does not matter
 
782
        return {"org.freedesktop.DBus.Deprecated": "true",
 
783
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
784
                    "false"}
 
785
    """
 
786
    def decorator(func):
 
787
        func._dbus_is_interface = True
 
788
        func._dbus_interface = dbus_interface
 
789
        func._dbus_name = dbus_interface
 
790
        return func
 
791
    return decorator
 
792
 
 
793
 
774
794
class DBusPropertyException(dbus.exceptions.DBusException):
775
795
    """A base class for D-Bus property-related exceptions
776
796
    """
799
819
    """
800
820
    
801
821
    @staticmethod
802
 
    def _is_dbus_property(obj):
803
 
        return getattr(obj, "_dbus_is_property", False)
 
822
    def _is_dbus_thing(thing):
 
823
        """Returns a function testing if an attribute is a D-Bus thing
 
824
        
 
825
        If called like _is_dbus_thing("method") it returns a function
 
826
        suitable for use as predicate to inspect.getmembers().
 
827
        """
 
828
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
829
                                   False)
804
830
    
805
 
    def _get_all_dbus_properties(self):
 
831
    def _get_all_dbus_things(self, thing):
806
832
        """Returns a generator of (name, attribute) pairs
807
833
        """
808
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
834
        return ((athing.__get__(self)._dbus_name,
 
835
                 athing.__get__(self))
809
836
                for cls in self.__class__.__mro__
810
 
                for name, prop in
811
 
                inspect.getmembers(cls, self._is_dbus_property))
 
837
                for name, athing in
 
838
                inspect.getmembers(cls,
 
839
                                   self._is_dbus_thing(thing)))
812
840
    
813
841
    def _get_dbus_property(self, interface_name, property_name):
814
842
        """Returns a bound method if one exists which is a D-Bus
816
844
        """
817
845
        for cls in  self.__class__.__mro__:
818
846
            for name, value in (inspect.getmembers
819
 
                                (cls, self._is_dbus_property)):
 
847
                                (cls,
 
848
                                 self._is_dbus_thing("property"))):
820
849
                if (value._dbus_name == property_name
821
850
                    and value._dbus_interface == interface_name):
822
851
                    return value.__get__(self)
864
893
        Note: Will not include properties with access="write".
865
894
        """
866
895
        properties = {}
867
 
        for name, prop in self._get_all_dbus_properties():
 
896
        for name, prop in self._get_all_dbus_things("property"):
868
897
            if (interface_name
869
898
                and interface_name != prop._dbus_interface):
870
899
                # Interface non-empty but did not match
885
914
                         path_keyword='object_path',
886
915
                         connection_keyword='connection')
887
916
    def Introspect(self, object_path, connection):
888
 
        """Standard D-Bus method, overloaded to insert property tags.
 
917
        """Overloading of standard D-Bus method.
 
918
        
 
919
        Inserts property tags and interface annotation tags.
889
920
        """
890
921
        xmlstring = dbus.service.Object.Introspect(self, object_path,
891
922
                                                   connection)
898
929
                e.setAttribute("access", prop._dbus_access)
899
930
                return e
900
931
            for if_tag in document.getElementsByTagName("interface"):
 
932
                # Add property tags
901
933
                for tag in (make_tag(document, name, prop)
902
934
                            for name, prop
903
 
                            in self._get_all_dbus_properties()
 
935
                            in self._get_all_dbus_things("property")
904
936
                            if prop._dbus_interface
905
937
                            == if_tag.getAttribute("name")):
906
938
                    if_tag.appendChild(tag)
 
939
                # Add interface annotation tags
 
940
                for annotation, value in dict(
 
941
                    itertools.chain(
 
942
                        *(annotations().iteritems()
 
943
                          for name, annotations in
 
944
                          self._get_all_dbus_things("interface")
 
945
                          if name == if_tag.getAttribute("name")
 
946
                          ))).iteritems():
 
947
                    attr_tag = document.createElement("annotation")
 
948
                    attr_tag.setAttribute("name", annotation)
 
949
                    attr_tag.setAttribute("value", value)
 
950
                    if_tag.appendChild(attr_tag)
907
951
                # Add the names to the return values for the
908
952
                # "org.freedesktop.DBus.Properties" methods
909
953
                if (if_tag.getAttribute("name")
944
988
    def __new__(mcs, name, bases, attr):
945
989
        # Go through all the base classes which could have D-Bus
946
990
        # methods, signals, or properties in them
 
991
        old_interface_names = []
947
992
        for base in (b for b in bases
948
993
                     if issubclass(b, dbus.service.Object)):
949
994
            # Go though all attributes of the base class
959
1004
                alt_interface = (attribute._dbus_interface
960
1005
                                 .replace("se.recompile.Mandos",
961
1006
                                          "se.bsnet.fukt.Mandos"))
 
1007
                if alt_interface != attribute._dbus_interface:
 
1008
                    old_interface_names.append(alt_interface)
962
1009
                # Is this a D-Bus signal?
963
1010
                if getattr(attribute, "_dbus_is_signal", False):
964
1011
                    # Extract the original non-method function by
1031
1078
                                        attribute.func_name,
1032
1079
                                        attribute.func_defaults,
1033
1080
                                        attribute.func_closure)))
 
1081
                # Is this a D-Bus interface?
 
1082
                elif getattr(attribute, "_dbus_is_interface", False):
 
1083
                    # Create a new, but exactly alike, function
 
1084
                    # object.  Decorate it to be a new D-Bus interface
 
1085
                    # with the alternate D-Bus interface name.  Add it
 
1086
                    # to the class.
 
1087
                    attr[attrname] = (dbus_interface_annotations
 
1088
                                      (alt_interface)
 
1089
                                      (types.FunctionType
 
1090
                                       (attribute.func_code,
 
1091
                                        attribute.func_globals,
 
1092
                                        attribute.func_name,
 
1093
                                        attribute.func_defaults,
 
1094
                                        attribute.func_closure)))
 
1095
        # Deprecate all old interfaces
 
1096
        basename="_AlternateDBusNamesMetaclass_interface_annotation{0}"
 
1097
        for old_interface_name in old_interface_names:
 
1098
            @dbus_interface_annotations(old_interface_name)
 
1099
            def func(self):
 
1100
                return { "org.freedesktop.DBus.Deprecated": "true" }
 
1101
            # Find an unused name
 
1102
            for aname in (basename.format(i) for i in
 
1103
                          itertools.count()):
 
1104
                if aname not in attr:
 
1105
                    attr[aname] = func
 
1106
                    break
1034
1107
        return type.__new__(mcs, name, bases, attr)
1035
1108
 
1036
1109
 
1186
1259
    ## D-Bus methods, signals & properties
1187
1260
    _interface = "se.recompile.Mandos.Client"
1188
1261
    
 
1262
    ## Interfaces
 
1263
    
 
1264
    @dbus_interface_annotations(_interface)
 
1265
    def _foo(self):
 
1266
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
1267
                     "false"}
 
1268
    
1189
1269
    ## Signals
1190
1270
    
1191
1271
    # CheckerCompleted - signal
2317
2397
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2318
2398
    
2319
2399
    if use_dbus:
2320
 
        class MandosDBusService(dbus.service.Object):
 
2400
        class MandosDBusService(DBusObjectWithProperties):
2321
2401
            """A D-Bus proxy object"""
2322
2402
            def __init__(self):
2323
2403
                dbus.service.Object.__init__(self, bus, "/")
2324
2404
            _interface = "se.recompile.Mandos"
2325
2405
            
 
2406
            @dbus_interface_annotations(_interface)
 
2407
            def _foo(self):
 
2408
                return { "org.freedesktop.DBus.Property"
 
2409
                         ".EmitsChangedSignal":
 
2410
                             "false"}
 
2411
            
2326
2412
            @dbus.service.signal(_interface, signature="o")
2327
2413
            def ClientAdded(self, objpath):
2328
2414
                "D-Bus signal"