diff options
Diffstat (limited to 'services')
-rw-r--r-- | services/presence/PresenceService.py | 124 | ||||
-rw-r--r-- | services/presence/Service.py | 119 |
2 files changed, 182 insertions, 61 deletions
diff --git a/services/presence/PresenceService.py b/services/presence/PresenceService.py index 1534ae9..c04084c 100644 --- a/services/presence/PresenceService.py +++ b/services/presence/PresenceService.py @@ -56,6 +56,38 @@ class ServiceAdv(object): raise ValueError("Can't reset to resolve pending from resolved.") self._state = state +class RegisteredServiceType(object): + def __init__(self, stype): + self._stype = stype + self._refcount = 1 + + def get_type(self): + return self._stype + + def ref(self): + self._refcount += 1 + + def unref(self): + self._refcount -= 1 + return self._refcount + +def _txt_to_dict(txt): + """Convert an avahi-returned TXT record formatted + as nested arrays of integers (from dbus) into a dict + of key/value string pairs.""" + prop_dict = {} + props = avahi.txt_array_to_string_array(txt) + for item in props: + key = value = None + if '=' not in item: + # No = means a boolean value of true + key = item + value = True + else: + (key, value) = item.split('=') + prop_dict[key] = value + return prop_dict + _PRESENCE_SERVICE = "org.laptop.Presence" _PRESENCE_DBUS_INTERFACE = "org.laptop.Presence" @@ -169,8 +201,9 @@ class PresenceServiceDBusHelper(dbus.service.Object): return owner.object_path() @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="os", out_signature="o") - def joinActivity(self, activity_op, stype): + in_signature="os", out_signature="o", + sender_keyword="sender") + def joinActivity(self, activity_op, stype, sender): found_activity = None acts = self._parent.get_activities() for act in acts: @@ -179,29 +212,34 @@ class PresenceServiceDBusHelper(dbus.service.Object): break if not found_activity: raise NotFoundError("The activity %s was not found." % activity_op) - return self._parent.join_activity(found_activity, stype) + return self._parent.join_activity(found_activity, stype, sender) @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="ssa{ss}sis", out_signature="o") - def shareActivity(self, activity_id, stype, properties, address, port, domain): + in_signature="ssa{ss}sis", out_signature="o", + sender_keyword="sender") + def shareActivity(self, activity_id, stype, properties, address, port, + domain, sender=None): if not len(address): address = None service = self._parent.share_activity(activity_id, stype, properties, address, - port, domain) + port, domain, sender) return service.object_path() @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="ssa{ss}sis", out_signature="o") - def registerService(self, name, stype, properties, address, port, domain): + in_signature="ssa{ss}sis", out_signature="o", + sender_keyword="sender") + def registerService(self, name, stype, properties, address, port, domain, + sender=None): if not len(address): address = None service = self._parent.register_service(name, stype, properties, address, - port, domain) + port, domain, sender) return service.object_path() @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="o", out_signature="") - def unregisterService(self, service_op): + in_signature="o", out_signature="", + sender_keyword="sender") + def unregisterService(self, service_op, sender): found_serv = None services = self._parent.get_services() for serv in services: @@ -210,7 +248,7 @@ class PresenceServiceDBusHelper(dbus.service.Object): break if not found_serv: raise NotFoundError("The activity %s was not found." % service_op) - return self._parent.unregister_service(found_serv) + return self._parent.unregister_service(found_serv, sender) @dbus.service.method(_PRESENCE_DBUS_INTERFACE, in_signature="s", out_signature="") @@ -424,9 +462,10 @@ class PresenceService(object): key = (full_name, stype) if not self._services.has_key(key): objid = self._get_next_object_id() + props = _txt_to_dict(txt) service = Service.Service(self._bus_name, objid, name=full_name, stype=stype, domain=domain, address=address, port=port, - properties=txt, source_address=address, local=adv.is_local()) + properties=props, source_address=address, local=adv.is_local()) self._services[key] = service else: # Already tracking this service; likely we were the one that shared it @@ -588,19 +627,22 @@ class PresenceService(object): def _new_domain_cb_glue(self, interface, protocol, domain, flags=0): gobject.idle_add(self._new_domain_cb, interface, protocol, domain, flags) - def join_activity(self, activity, stype): + def join_activity(self, activity, stype, sender): services = activity.get_services_of_type(stype) if not len(services): - raise NotFoundError("The service type %s was not present within the activity %s" % (stype, activity.object_path())) + raise NotFoundError("The service type %s was not present within " \ + "the activity %s" % (stype, activity.object_path())) act_service = services[0] props = act_service.get_properties() color = activity.get_color() if color: props['color'] = color return self._share_activity(activity.get_id(), stype, properties, - act_service.get_address(), act_service.get_port(), act_service.get_domain()) + act_service.get_address(), act_service.get_port(), + act_service.get_domain(), sender) - def share_activity(self, activity_id, stype, properties=None, address=None, port=-1, domain=u"local"): + def share_activity(self, activity_id, stype, properties=None, address=None, + port=-1, domain=u"local", sender=None): """Convenience function to share an activity with other buddies.""" if not util.validate_activity_id(activity_id): raise ValueError("invalid activity id") @@ -621,14 +663,19 @@ class PresenceService(object): if color: properties['color'] = color - logging.debug('Share activity %s, type %s, address %s, port %d, properties %s' % (activity_id, stype, address, port, properties)) - return self.register_service(real_name, stype, properties, address, port, domain) + logging.debug('Share activity %s, type %s, address %s, port %d, " \ + "properties %s' % (activity_id, stype, address, port, + properties)) + return self.register_service(real_name, stype, properties, address, + port, domain, sender) - def register_service(self, name, stype, properties={}, address=None, port=-1, domain=u"local"): + def register_service(self, name, stype, properties={}, address=None, + port=-1, domain=u"local", sender=None): """Register a new service, advertising it to other Buddies on the network.""" (actid, person_name) = Service.decompose_service_name(name) if self.get_owner() and person_name != self.get_owner().get_name(): - raise RuntimeError("Tried to register a service that didn't have Owner nick as the service name!") + raise RuntimeError("Tried to register a service that didn't have" \ + " Owner nick as the service name!") if not domain or not len(domain): domain = u"local" if not port or port == -1: @@ -647,12 +694,14 @@ class PresenceService(object): objid = self._get_next_object_id() service = Service.Service(self._bus_name, objid, name=name, stype=stype, domain=domain, address=address, port=port, - properties=properties, source_address=None, local=True) + properties=properties, source_address=None, local=True, + local_publisher=sender) self._services[(name, stype)] = service port = service.get_port() logging.debug("PS: Will register service with name='%s', stype='%s'," \ - " domain='%s', address='%s', port=%d, info='%s'" % (name, stype, domain, address, port, info)) + " domain='%s', address='%s', port=%d, info='%s'" % (name, stype, + domain, address, port, info)) group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, dbus.String(name), dbus.String(stype), dbus.String(domain), dbus.String(""), # let Avahi figure the 'host' out dbus.UInt16(port), info) @@ -667,7 +716,10 @@ class PresenceService(object): self.register_service_type(stype) return service - def unregister_service(self, service): + def unregister_service(self, service, sender=None): + local_publisher = service.get_local_publisher() + if sender is not None and local_publisher != sender: + raise ValueError("Service was not not registered by requesting process!") group = service.get_avahi_entry_group() if not group: raise ValueError("Service was not a local service provided by this laptop!") @@ -678,9 +730,16 @@ class PresenceService(object): a certain mDNS service types.""" if type(stype) != type(u""): raise ValueError("service type must be a unicode string.") - if stype in self._registered_service_types: - return - self._registered_service_types.append(stype) + + # If we've already registered it as a service type, ref it and return + for item in self._registered_service_types: + if item.get_type() == stype: + item.ref() + return + + # Otherwise track this type now + obj = RegisteredServiceType(stype) + self._registered_service_types.append(obj) # Find unresolved services that match the service type # we're now interested in, and resolve them @@ -698,8 +757,15 @@ class PresenceService(object): """Stop tracking a certain mDNS service.""" if type(stype) != type(u""): raise ValueError("service type must be a unicode string.") - if stype in self._registered_service_types: - self._registered_service_types.remove(stype) + item = None + for item in self._registered_service_types: + if item.get_type() == stype: + break + # if it was found, unref it and possibly remove it + if item is not None: + if item.unref() <= 0: + self._registered_service_types.remove(item) + del item def main(): diff --git a/services/presence/Service.py b/services/presence/Service.py index 3c65d99..0c23600 100644 --- a/services/presence/Service.py +++ b/services/presence/Service.py @@ -5,23 +5,6 @@ from sugar import util import dbus, dbus.service import random -def _txt_to_dict(txt): - """Convert an avahi-returned TXT record formatted - as nested arrays of integers (from dbus) into a dict - of key/value string pairs.""" - prop_dict = {} - props = avahi.txt_array_to_string_array(txt) - for item in props: - key = value = None - if '=' not in item: - # No = means a boolean value of true - key = item - value = True - else: - (key, value) = item.split('=') - prop_dict[key] = value - return prop_dict - def compose_service_name(name, activity_id): if type(name) == type(""): name = unicode(name) @@ -67,6 +50,11 @@ class ServiceDBusHelper(dbus.service.Object): self._object_path = object_path dbus.service.Object.__init__(self, bus_name, self._object_path) + @dbus.service.signal(SERVICE_DBUS_INTERFACE, + signature="as") + def PublishedValueChanged(self, keylist): + pass + @dbus.service.method(SERVICE_DBUS_INTERFACE, in_signature="", out_signature="a{sv}") def getProperties(self): @@ -90,14 +78,33 @@ class ServiceDBusHelper(dbus.service.Object): return pary @dbus.service.method(SERVICE_DBUS_INTERFACE, - in_signature="s", out_signature="s") + in_signature="s") def getPublishedValue(self, key): """Return the value belonging to the requested key from the service's TXT records.""" - value = self._parent.get_one_property(key) - if type(value) == type(True): - value = str(value) - return value + val = self._parent.get_one_property(key) + if not val: + raise KeyError("Value was not found.") + return val + + @dbus.service.method(SERVICE_DBUS_INTERFACE, + in_signature="", out_signature="a{sv}") + def getPublishedValues(self): + pary = {} + props = self._parent.get_properties() + for key, value in props.items(): + pary[key] = str(value) + return dbus.Dictionary(pary) + + @dbus.service.method(SERVICE_DBUS_INTERFACE, + sender_keyword="sender") + def setPublishedValue(self, key, value, sender): + self._parent.set_property(key, value, sender) + + @dbus.service.method(SERVICE_DBUS_INTERFACE, + in_signature="a{sv}", sender_keyword="sender") + def setPublishedValues(self, values, sender): + self._parent.set_properties(values, sender) class Service(object): @@ -105,7 +112,7 @@ class Service(object): service as advertised on the network.""" def __init__(self, bus_name, object_id, name, stype, domain=u"local", address=None, port=-1, properties=None, source_address=None, - local=False): + local=False, local_publisher=None): if not bus_name: raise ValueError("DBus bus name must be valid") if not object_id or type(object_id) != type(1): @@ -137,7 +144,7 @@ class Service(object): self._port = -1 self.set_port(port) self._properties = {} - self.set_properties(properties) + self._internal_set_properties(properties, first_time=True) self._avahi_entry_group = None self._local = local @@ -159,13 +166,19 @@ class Service(object): if self._properties.has_key(_ACTIVITY_ID_TAG): prop_actid = self._properties[_ACTIVITY_ID_TAG] if (prop_actid and not actid) or (prop_actid != actid): - raise ValueError("ActivityID property specified, but the service names's activity ID didn't match it: %s, %s" % (prop_actid, actid)) + raise ValueError("ActivityID property specified, but the " \ + "service names's activity ID didn't match it: %s," \ + " %s" % (prop_actid, actid)) self._activity_id = actid if actid and not self._properties.has_key(_ACTIVITY_ID_TAG): self._properties[_ACTIVITY_ID_TAG] = actid self._owner = None + # ID of the D-Bus connection that published this service, if any. + # We only let the local publisher modify the service. + self._local_publisher = local_publisher + # register ourselves with dbus self._object_id = object_id self._object_path = SERVICE_DBUS_OBJECT_PATH + str(self._object_id) @@ -182,6 +195,9 @@ class Service(object): raise RuntimeError("Can only set a service's owner once") self._owner = owner + def get_local_publisher(self): + return self._local_publisher + def is_local(self): return self._local @@ -207,19 +223,55 @@ class Service(object): properties.""" return self._properties - def set_properties(self, properties): + def set_property(self, key, value, sender=None): + """Set one service property""" + if sender is not None and self._local_publisher != sender: + raise ValueError("Service was not not registered by requesting process!") + + if type(key) != type(u""): + raise ValueError("Key must be a unicode string.") + if type(value) != type(u"") or type(value) != type(True): + raise ValueError("Key must be a unicode string or a boolean.") + + # Ignore setting the key to it's current value + if self._properties.has_key(key): + if self._properties[key] == value: + return + + # Blank value means remove key + remove = False + if type(value) == type(u"") and len(value) == 0: + remove = True + if type(value) == type(False) and value == False: + remove = True + + if remove: + # If the key wasn't present, return without error + if self._properties.has_key(key): + del self._properties[key] + else: + # Otherwise set it + if type(value) == type(True): + value = "" + self._properties[key] = value + self._dbus_helper.PublishedValueChanged(key) + + def set_properties(self, properties, sender=None): + """Set all service properties in one call""" + if sender is not None and self._local_publisher != sender: + raise ValueError("Service was not not registered by requesting process!") + self._internal_set_properties(properties) + + def _internal_set_properties(self, properties, first_time=False): """Set the service's properties from either an Avahi TXT record (a list of lists of integers), or a python dictionary.""" + if type(properties) != type({}): + raise ValueError("Properties must be a dictionary.") self._properties = {} - props = {} - if type(properties) == type([]): - props = _txt_to_dict(properties) - elif type(properties) == type({}): - props = properties # Set key/value pairs on internal property list - for key, value in props.items(): + for key, value in properties.items(): if len(key) == 0: continue tmp_key = key @@ -230,6 +282,9 @@ class Service(object): tmp_val = unicode(tmp_val) self._properties[tmp_key] = tmp_val + if not first_time: + self._dbus_helper.PublishedValueChanged(self._properties.keys()) + def get_type(self): """Return the service's service type.""" return self._stype |