diff --git a/sugar/presence/PresenceService.py b/sugar/presence/PresenceService.py index 0aeb7114..e4bbd237 100644 --- a/sugar/presence/PresenceService.py +++ b/sugar/presence/PresenceService.py @@ -69,9 +69,7 @@ class PresenceService(gobject.GObject): 'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), 'activity-announced': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), - 'new-service-adv': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_STRING, gobject.TYPE_STRING])) + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])) } __lock = threading.Lock() @@ -106,7 +104,8 @@ class PresenceService(gobject.GObject): self._activity_services = {} # All the mdns service types we care about - self._allowed_service_types = [] + self._allowed_service_types = [] # Short service type + self._allowed_activities = [] # activity UID # Keep track of stuff we're already browsing with ZC self._service_type_browsers = {} @@ -115,9 +114,6 @@ class PresenceService(gobject.GObject): # Resolved service list self._service_advs = [] - # Main activity UID to filter services on - self._activity_uids = [] - self._bus = dbus.SystemBus() self._server = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) @@ -213,7 +209,7 @@ class PresenceService(gobject.GObject): def _handle_new_service_for_activity(self, service, buddy): # If the serivce is a group service, merge it into our groups list - (uid, ignore) = service.get_activity_uid() + uid = service.get_activity_uid() if not uid: uid = "*" if not self._activity_services.has_key(uid): @@ -222,7 +218,7 @@ class PresenceService(gobject.GObject): self.emit('activity-announced', service, buddy) def _handle_remove_service_for_activity(self, service, buddy): - (uid, ignore) = service.get_activity_uid() + uid = service.get_activity_uid() if not uid: uid = "*" if self._activity_services.has_key(uid): @@ -231,28 +227,34 @@ class PresenceService(gobject.GObject): except: pass - def _resolve_service_reply_cb(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags): + def _resolve_service_reply_cb(self, interface, protocol, name, full_stype, domain, host, aprotocol, address, port, txt, flags): """When the service discovery finally gets here, we've got enough information about the service to assign it to a buddy.""" - self._log("resolved service '%s' type '%s' domain '%s' to %s:%s" % (name, stype, domain, address, port)) + self._log("resolved service '%s' type '%s' domain '%s' to %s:%s" % (name, full_stype, domain, address, port)) + + name = name.encode() + full_stype = full_stype.encode() + domain = domain.encode() + host = host.encode() + address = address.encode() # If this service was previously unresolved, remove it from the # unresolved list - adv_list = self._find_service_adv(interface=interface, protocol=protocol, name=name, - stype=stype, domain=domain) + adv_list = self._find_service_adv(interface=interface, protocol=protocol, + name=name, stype=full_stype, domain=domain) if not adv_list: return False adv = adv_list[0] adv.set_resolved(True) # Update the service now that it's been resolved - service = Service.Service(name=name, stype=stype, domain=domain, + service = Service.Service(name=name, stype=full_stype, domain=domain, address=address, port=port, properties=txt) adv.set_service(service) # Merge the service into our buddy and group lists, if needed buddy = self._handle_new_service_for_buddy(service) - (uid, ignore) = service.get_activity_uid() + uid = service.get_activity_uid() if buddy and uid: self._handle_new_service_for_activity(service, buddy) @@ -272,15 +274,15 @@ class PresenceService(gobject.GObject): error_handler=self._resolve_service_error_handler) return False - def _service_appeared_cb(self, interface, protocol, name, stype, domain, flags): - self._log("found service '%s' (%d) of type '%s' in domain '%s' on %i.%i." % (name, flags, stype, domain, interface, protocol)) + def _service_appeared_cb(self, interface, protocol, name, full_stype, domain, flags): + self._log("found service '%s' (%d) of type '%s' in domain '%s' on %i.%i." % (name, flags, full_stype, domain, interface, protocol)) # Add the service to our unresolved services list adv_list = self._find_service_adv(interface=interface, protocol=protocol, - name=name, stype=stype, domain=domain) + name=name.encode(), stype=full_stype.encode(), domain=domain.encode()) if not adv_list: - adv = ServiceAdv(interface=interface, protocol=protocol, name=name, - stype=stype, domain=domain) + adv = ServiceAdv(interface=interface, protocol=protocol, name=name.encode(), + stype=full_stype.encode(), domain=domain.encode()) self._service_advs.append(adv) # Find out the IP address of this interface, if we haven't already @@ -292,30 +294,31 @@ class PresenceService(gobject.GObject): self._local_addrs[interface] = addr # Decompose service type if we can - (uid, stype) = Service._decompose_service_type(stype) - - # FIXME: find a better way of letting StartPage get all activity advertisements - self.emit('new-service-adv', uid, stype) + (uid, short_stype) = Service._decompose_service_type(full_stype.encode()) # If we care about the service right now, resolve it resolve = False - if uid in self._activity_uids: - if stype in self._allowed_service_types: + if uid in self._allowed_activities: + if short_stype in self._allowed_service_types: resolve = True - if self._is_special_service_type(stype): + if self._is_special_service_type(short_stype): resolve = True if resolve: - gobject.idle_add(self._resolve_service, interface, protocol, name, stype, domain, flags) + gobject.idle_add(self._resolve_service, interface, protocol, name, full_stype, domain, flags) return False def _service_appeared_cb_glue(self, interface, protocol, name, stype, domain, flags): gobject.idle_add(self._service_appeared_cb, interface, protocol, name, stype, domain, flags) - def _service_disappeared_cb(self, interface, protocol, name, stype, domain, flags): - self._log("service '%s' of type '%s' in domain '%s' on %i.%i disappeared." % (name, stype, domain, interface, protocol)) + def _service_disappeared_cb(self, interface, protocol, name, full_stype, domain, flags): + self._log("service '%s' of type '%s' in domain '%s' on %i.%i disappeared." % (name, full_stype, domain, interface, protocol)) + name = name.encode() + full_stype = full_stype.encode() + domain = domain.encode() + # If it's an unresolved service, remove it from our unresolved list adv_list = self._find_service_adv(interface=interface, protocol=protocol, - name=name, stype=stype, domain=domain) + name=name, stype=full_stype, domain=domain) if not adv_list: return False @@ -390,48 +393,58 @@ class PresenceService(gobject.GObject): def _new_domain_cb_glue(self, interface, protocol, domain, flags=0): gobject.idle_add(self._new_domain_cb, interface, protocol, domain, flags) - def track_service_type(self, stype): + def track_service_type(self, short_stype): """Requests that the Presence service look for and recognize a certain mDNS service types.""" if not self._started: raise RuntimeError("presence service must be started first.") - if type(stype) != type("") and type(stype) != type(u""): + if type(short_stype) == type(u""): + raise ValueError("service type should not be unicode.") + if type(short_stype) != type(""): raise ValueError("service type must be a string.") - if type(stype) == type(u""): - stype = stype.encode() - if self._is_special_service_type(stype): + if self._is_special_service_type(short_stype): return - if stype in self._allowed_service_types: + if short_stype in self._allowed_service_types: return # Decompose service type if we can - (uid, dec_stype) = Service._decompose_service_type(stype) - if uid and util.validate_activity_uid(uid): - if uid not in self._activity_uids: - self._activity_uids.append(uid) + (uid, dec_stype) = Service._decompose_service_type(short_stype) + if uid: + raise RuntimeError("Can only track plain service types!") self._allowed_service_types.append(dec_stype) # Find unresolved services that match the service type # we're now interested in, and resolve them - adv_list = self._find_service_adv(stype=stype) - for adv in adv_list: + resolv_list = [] + # Find all services first by their activity + for uid in self._allowed_activities: + full_stype = Service.compose_service_type(dec_stype, uid) + adv_list = self._find_service_adv(stype=full_stype) + resolv_list = resolv_list + adv_list + # Then, find services by just the plain service type + resolv_list = resolv_list + self._find_service_adv(stype=dec_stype) + + # Request resolution for them + for adv in resolv_list: gobject.idle_add(self._resolve_service, adv.interface(), adv.protocol(), adv.name(), adv.stype(), adv.domain(), 0) - def untrack_service_type(self, stype): + def untrack_service_type(self, short_stype): """Stop tracking a certain mDNS service.""" if not self._started: raise RuntimeError("presence service must be started first.") - if not type(stype) == type(""): + if type(short_stype) == type(u""): + raise ValueError("service type should not be unicode.") + if not type(short_stype) == type(""): raise ValueError("service type must be a string.") # Decompose service type if we can - (uid, stype) = Service._decompose_service_type(stype) - if uid and util.validate_activity_uid(uid): - if uid in self._activity_uids: - self._activity_uids.remove(uid) - if stype in self._allowed_service_types: - self._allowed_service_types.remove(stype) + (uid, dec_stype) = Service._decompose_service_type(short_stype) + if uid: + raise RuntimeError("Can only untrack plain service types!") + + if dec_stype in self._allowed_service_types: + self._allowed_service_types.remove(dec_stype) def join_shared_activity(self, service): """Convenience function to join a group and notify other buddies @@ -442,9 +455,13 @@ class PresenceService(gobject.GObject): def share_activity(self, activity, stype, properties={}, address=None, port=None): """Convenience function to share an activity with other buddies.""" + print "type stype == %s" % type(stype) + print "Type of a string is %s" % type("") + uid = activity.get_id() owner_nick = self._owner.get_nick_name() real_stype = Service.compose_service_type(stype, uid) + print "PS: type of real_stype is %s" % type(real_stype) if address and type(address) != type(""): raise ValueError("address must be a valid string.") if not address: @@ -470,7 +487,7 @@ class PresenceService(gobject.GObject): raise RuntimeError("presence service must be started first.") rs_name = service.get_name() - rs_stype = service.get_type() + rs_stype = service.get_full_type() rs_port = service.get_port() if type(rs_port) != type(1) and (rs_port <= 1024 or rs_port > 65536): raise ValueError("invalid service port.") @@ -493,7 +510,8 @@ class PresenceService(gobject.GObject): # should un-register it an re-register with the correct info if str(exc) == "Local name collision": pass - (uid, activity_stype) = service.get_activity_uid() + uid = service.get_activity_uid() + activity_stype = service.get_type() self.track_service_type(activity_stype) return group diff --git a/sugar/presence/Service.py b/sugar/presence/Service.py index 2288936b..a8461ac4 100644 --- a/sugar/presence/Service.py +++ b/sugar/presence/Service.py @@ -22,9 +22,12 @@ def _txt_to_dict(txt): def compose_service_type(stype, activity_uid): if not activity_uid: return stype - if not stype or (type(stype) != type("") and type(stype) != type(u"")): + if type(stype) == type(u""): + raise ValueError("stype must not be in unicode.") + if not stype or type(stype) != type(""): raise ValueError("stype must be a valid string.") - return "_%s_%s" % (activity_uid, stype) + composed = "_%s_%s" % (activity_uid, stype) + return composed.encode() def _decompose_service_type(stype): """Break a service type into the UID and real service type, if we can.""" @@ -60,33 +63,35 @@ class Service(object): """Encapsulates information about a specific ZeroConf/mDNS service as advertised on the network.""" def __init__(self, name, stype, domain, address=None, port=-1, properties=None): + full_stype = stype # Validate immutable options - if not name or (type(name) != type("") and type(name) != type(u"")) or not len(name): + if name and type(name) == type(u""): + raise ValueError("name must not be in unicode.") + if not name or type(name) != type("") or not len(name): raise ValueError("must specify a valid service name.") - if not stype or (type(stype) != type("") and type(stype) != type(u"")) or not len(stype): + print "Service:: type of full_stype is %s" % type(full_stype) + if full_stype and type(full_stype) == type(u""): + raise ValueError("service type must not be in unicode.") + if not full_stype or type(full_stype) != type("") or not len(full_stype): raise ValueError("must specify a service type.") if not stype.endswith("._tcp") and not stype.endswith("._udp"): raise ValueError("must specify a TCP or UDP service type.") - if type(domain) != type("") and type(domain) != type(u""): + if domain and type(domain) == type(u""): + raise ValueError("domain must not be in unicode.") + if type(domain) != type(""): raise ValueError("must specify a domain.") - if len(domain) and domain != "local" and domain != u"local": + if len(domain) and domain != "local": raise ValueError("must use the 'local' domain (for now).") - if type(stype) == type(u""): - stype = stype.encode() - (uid, real_stype) = _decompose_service_type(stype) + (uid, short_stype) = _decompose_service_type(full_stype) if uid and not util.validate_activity_uid(uid): raise ValueError("service type activity uid not a valid activity UID.") - if type(name) == type(u""): - name = name.encode() self._name = name - self._stype = stype - self._activity_stype = real_stype - if type(domain) == type(u""): - domain = domain.encode() + self._full_stype = full_stype + self._activity_stype = short_stype self._domain = domain self._address = None self.set_address(address) @@ -143,8 +148,16 @@ class Service(object): self._properties[key] = self._properties[key].encode() def get_type(self): - """Return the service's service type.""" - return self._stype + """Return the service's service type without any activity identifiers.""" + return self._activity_stype + + def get_full_type(self): + """Return the service's full service type as seen over the network.""" + return self._full_stype + + def get_activity_uid(self): + """Return the activity UID this service is associated with, if any.""" + return self._activity_uid def get_port(self): return self._port @@ -171,10 +184,6 @@ class Service(object): """Return the ZeroConf/mDNS domain the service was found in.""" return self._domain - def get_activity_uid(self): - """Return the activity UID this service is associated with, if any.""" - return (self._activity_uid, self._activity_stype) - ################################################################# # Tests diff --git a/sugar/shell/StartPage.py b/sugar/shell/StartPage.py index 2cc7ceff..6d548b43 100644 --- a/sugar/shell/StartPage.py +++ b/sugar/shell/StartPage.py @@ -20,7 +20,7 @@ class ActivitiesModel(gtk.ListStore): self.append([ title, address, None, None ]) def add_activity(self, buddy, service): - (uid, stype) = service.get_activity_uid() + uid = service.get_activity_uid() title = urllib.unquote(service.get_one_property('Title')) address = urllib.unquote(service.get_one_property('URI')) subtitle = 'Shared by %s' % buddy.get_nick_name() @@ -75,7 +75,6 @@ class StartPage(gtk.HBox): self._pservice = PresenceService.get_instance() self._pservice.connect("activity-announced", self._on_activity_announced_cb) - self._pservice.connect("new-service-adv", self._on_new_service_adv_cb) self._pservice.start() self._pservice.track_service_type(BrowserActivity._BROWSER_ACTIVITY_TYPE) @@ -115,12 +114,6 @@ class StartPage(gtk.HBox): self.pack_start(sw) sw.show() - def _on_new_service_adv_cb(self, pservice, uid, stype): - if uid is not None: - real_stype = Service.compose_service_type(stype, uid) - print "Will ask to track uid=%s, stype=%s" % (uid, stype) - self._pservice.track_service_type(real_stype) - def _on_activity_announced_cb(self, pservice, service, buddy): self._activities.get_model().add_activity(buddy, service)