diff options
Diffstat (limited to 'services/presence/PresenceService.py')
-rw-r--r-- | services/presence/PresenceService.py | 122 |
1 files changed, 65 insertions, 57 deletions
diff --git a/services/presence/PresenceService.py b/services/presence/PresenceService.py index e541387..976d5e0 100644 --- a/services/presence/PresenceService.py +++ b/services/presence/PresenceService.py @@ -29,6 +29,11 @@ class ServiceAdv(object): raise ValueError("local must be a boolean.") self._local = local self._state = _SA_UNRESOLVED + self._resolver = None + + def __del__(self): + if self._resolver: + del self._resolver def interface(self): return self._interface @@ -47,7 +52,14 @@ class ServiceAdv(object): def set_service(self, service): if not isinstance(service, Service.Service): raise ValueError("must be a valid service.") - self._service = service + if service != self._service: + self._service = service + def resolver(self): + return self._resolver + def set_resolver(self, resolver): + if not isinstance(resolver, dbus.Interface): + raise ValueError("must be a valid dbus object") + self._resolver = resolver def state(self): return self._state def set_state(self, state): @@ -452,12 +464,16 @@ class PresenceService(object): self._dbus_helper.ActivityDisappeared(activity.object_path()) del self._activities[actid] - def _resolve_service_reply_cb(self, adv, interface, protocol, full_name, - stype, domain, host, aprotocol, address, port, txt, flags): + def _service_resolved_cb(self, adv, interface, protocol, full_name, + stype, domain, host, aprotocol, address, port, txt, flags, + updated): """When the service discovery finally gets here, we've got enough information about the service to assign it to a buddy.""" - logging.debug("Resolved service '%s' type '%s' domain '%s' to " \ - " %s:%s" % (full_name, stype, domain, address, port)) + tag = "Resolved" + if updated: + tag = "Updated" + logging.debug("%s service '%s' type '%s' domain '%s' to " \ + " %s:%s" % (tag, full_name, stype, domain, address, port)) if not adv in self._service_advs: return False @@ -465,25 +481,33 @@ class PresenceService(object): return False # See if we know about this service already + service = None key = (full_name, stype) + props = _txt_to_dict(txt) 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=props, source_address=address, local=adv.is_local()) + properties=props, source_address=address) self._services[key] = service else: - # Already tracking this service; likely we were the one that shared it - # in the first place, and therefore the source address would not have - # been set yet + # Already tracking this service; either: + # a) we were the one that shared it in the first place, + # and therefore the source address would not have + # been set yet + # b) the service has been updated service = self._services[key] if not service.get_source_address(): service.set_source_address(address) if not service.get_address(): service.set_address(address) + adv.set_service(service) + if service and updated: + service.set_properties(props, from_network=True) + return False + # Merge the service into our buddy and activity lists, if needed buddy = self._handle_new_service_for_buddy(service, adv.is_local()) if buddy and service.get_activity_id(): @@ -491,24 +515,34 @@ class PresenceService(object): return False - def _resolve_service_reply_cb_glue(self, adv, interface, protocol, name, + def _service_resolved_cb_glue(self, adv, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags): + # Avahi doesn't flag updates to existing services, so we have + # to determine that here + updated = False + if adv.state() == _SA_RESOLVED: + updated = True + adv.set_state(_SA_RESOLVED) - gobject.idle_add(self._resolve_service_reply_cb, adv, interface, + gobject.idle_add(self._service_resolved_cb, adv, interface, protocol, name, stype, domain, host, aprotocol, address, - port, txt, flags) + port, txt, flags, updated) - def _resolve_service_error_handler(self, adv, err): + def _service_resolved_failure_cb(self, adv, err): adv.set_state(_SA_UNRESOLVED) logging.error("Error resolving service %s.%s: %s" % (adv.name(), adv.stype(), err)) def _resolve_service(self, adv): """Resolve and lookup a ZeroConf service to obtain its address and TXT records.""" # Ask avahi to resolve this particular service - self._mdns_service.ResolveService(int(adv.interface()), int(adv.protocol()), adv.name(), - adv.stype(), adv.domain(), avahi.PROTO_UNSPEC, dbus.UInt32(0), - reply_handler=lambda *args: self._resolve_service_reply_cb_glue(adv, *args), - error_handler=lambda *args: self._resolve_service_error_handler(adv, *args)) + path = self._mdns_service.ServiceResolverNew(dbus.Int32(adv.interface()), + dbus.Int32(adv.protocol()), adv.name(), adv.stype(), adv.domain(), + avahi.PROTO_UNSPEC, dbus.UInt32(0)) + resolver = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, path), + avahi.DBUS_INTERFACE_SERVICE_RESOLVER) + resolver.connect_to_signal('Found', lambda *args: self._service_resolved_cb_glue(adv, *args)) + resolver.connect_to_signal('Failure', lambda *args: self._service_resolved_failure_cb(adv, *args)) + adv.set_resolver(resolver) return False def _service_appeared_cb(self, interface, protocol, full_name, stype, domain, flags): @@ -679,6 +713,11 @@ class PresenceService(object): 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.""" + # Refuse to register if we can't get the dbus connection this request + # came from for some reason + if not sender: + raise RuntimeError("Service registration request must have a sender.") + (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" \ @@ -688,49 +727,18 @@ class PresenceService(object): if not port or port == -1: port = random.randint(4000, 65000) - try: - obj = self._system_bus.get_object(avahi.DBUS_NAME, self._mdns_service.EntryGroupNew()) - group = dbus.Interface(obj, avahi.DBUS_INTERFACE_ENTRY_GROUP) - - # Add properties; ensure they are converted to ByteArray types - # because python sometimes can't figure that out - info = dbus.Array([], signature="aay") - for k, v in properties.items(): - info.append(dbus.types.ByteArray("%s=%s" % (k, v))) - - 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, - local_publisher=sender) - self._services[(name, stype)] = service - port = service.get_port() - - logging.debug("Will register service with name='%s', stype='%s'," \ - " 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) - service.set_avahi_entry_group(group) - group.Commit() - except dbus.exceptions.DBusException, exc: - # FIXME: ignore local name collisions, since that means - # the zeroconf service is already registered. Ideally we - # should un-register it an re-register with the correct info - if str(exc) == "Local name collision": - pass + 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_publisher=sender) + self._services[(name, stype)] = service self.register_service_type(stype) + service.register(self._system_bus, self._mdns_service) return 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 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!") - group.Free() + service.unregister(sender) def register_service_type(self, stype): """Requests that the Presence service look for and recognize |