From 1cdaf97f20e84ea9cb80e97708f10627e24b6988 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 14 Jun 2006 14:42:44 -0400 Subject: [PATCH] Switch from Groups to grouping services based on activity UID --- sugar/presence/Group.py | 61 ------------------------ sugar/presence/PresenceService.py | 77 ++++++++++++++++++++----------- sugar/presence/Service.py | 71 +++++++++++++++++++--------- sugar/util.py | 17 +++++++ 4 files changed, 116 insertions(+), 110 deletions(-) delete mode 100644 sugar/presence/Group.py diff --git a/sugar/presence/Group.py b/sugar/presence/Group.py deleted file mode 100644 index 2376978d..00000000 --- a/sugar/presence/Group.py +++ /dev/null @@ -1,61 +0,0 @@ -import Service - -import sugar.util - -def is_group_service_type(stype): - """Return True if the service type matches a group - service type, or False if it does not.""" - if stype.endswith("_group_olpc._tcp") or stype.endswith("_group_olpc._udp"): - return True - return False - -__GROUP_NAME_TAG = "Name" -__GROUP_RESOURCE_TAG = "Resource" - -def new_group_service(group_name, resource): - """Create a new service suitable for defining a new group.""" - if type(group_name) != type("") or not len(group_name): - raise ValueError("group name must be a valid string.") - if type(resource) != type("") or not len(resource): - raise ValueError("group resource must be a valid string.") - - # Create a randomized service type - data = "%s%s" % (group_name, resource) - stype = "_%s_group_olpc._udp" % sugar.util.unique_id(data) - - properties = {__GROUP_NAME_TAG: group_name, __GROUP_RESOURCE_TAG: resource } - owner_nick = "" - port = random.randint(5000, 65000) - # Use random currently unassigned multicast address - address = "232.%d.%d.%d" % (random.randint(0, 254), random.randint(1, 254), - random.randint(1, 254)) - service = Service.Service(owner_nick, stype, "local", address=address, - port=port, properties=properties) - return service - - -class Group(object): - """Represents a collection of buddies all interested in the same resource.""" - def __init__(self, service): - if not isinstance(service, Service.Service): - raise ValueError("service argument was not a Service object.") - if not service.is_group_service(): - raise ValueError("provided serivce was not a group service.") - name = service.get_one_property(__GROUP_NAME_TAG) - if name == None: - raise ValueError("provided service did not provide a group name.") - self._name = name - resource = service.get_one_property(__GROUP_RESOURCE_TAG) - if resource == None: - raise ValueError("provided service did not provide a group resource.") - self._resource = resource - self._service = service - - def get_name(self): - return self._name - - def get_service(self): - return self._service - - def get_resource(self): - return self._resource diff --git a/sugar/presence/PresenceService.py b/sugar/presence/PresenceService.py index 7a539b16..fd71de8f 100644 --- a/sugar/presence/PresenceService.py +++ b/sugar/presence/PresenceService.py @@ -2,9 +2,9 @@ import threading import avahi, dbus, dbus.glib, dbus.dbus_bindings, gobject import Buddy import Service -import Group import os - +import string +from sugar import util def _get_local_ip_address(ifname): """Call Linux specific bits to retrieve our own IP address.""" @@ -97,8 +97,8 @@ class PresenceService(gobject.GObject): # Our owner object self._owner = None - # group UID -> Group: groups we've found - self._groups = {} + # activity UID -> Service: services grouped by activity UID + self._activity_services = {} # All the mdns service types we care about self._allowed_service_types = [] @@ -110,7 +110,7 @@ class PresenceService(gobject.GObject): # Resolved service list self._service_advs = [] - # Main activity UID to filter on + # Main activity UID to filter services on self._activity_uid = None self._bus = dbus.SystemBus() @@ -126,8 +126,8 @@ class PresenceService(gobject.GObject): self._started = True self._lock.release() - if activity_uid and (not type(activity_uid) == type("") or not len(activity_uid)): - raise ValueError("activity uid must be a string.") + if activity_uid and not util.validate_activity_uid(activity_uid): + raise ValueError("activity uid must be a valid UID string.") self._activity_uid = activity_uid # Always browse .local @@ -177,8 +177,6 @@ class PresenceService(gobject.GObject): type, and False if it's not.""" if stype == Buddy.PRESENCE_SERVICE_TYPE: return True - if Group.is_group_service_type(stype): - return True return False def _handle_new_service_for_buddy(self, service): @@ -204,12 +202,24 @@ class PresenceService(gobject.GObject): self.emit("buddy-appeared", buddy) return buddy - def _handle_new_service_for_group(self, service, buddy): + def _handle_new_service_for_activity(self, service, buddy): # If the serivce is a group service, merge it into our groups list - group = None - if not self._groups.has_key(service.get_type()): - group = Group.Group(service) - self._groups[service.get_type()] = group + uid = service.get_activity_uid() + if not uid: + uid = "*" + if not self._activity_services.has_key(uid): + self._activity_services[uid] = [] + self._activity_services[uid].append((buddy, service)) + + def _handle_remove_service_for_activity(self, service, buddy): + uid = service.get_activity_uid() + if not uid: + uid = "*" + if self._activity_services.has_key(uid): + try: + self._activity_services.remove((buddy, service)) + except: + pass def _resolve_service_reply_cb(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags): """When the service discovery finally gets here, we've got enough information about the @@ -226,13 +236,14 @@ class PresenceService(gobject.GObject): adv.set_resolved(True) # Update the service now that it's been resolved - service = Service.Service(name, stype, domain, address, port, txt) + service = Service.Service(name=name, stype=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) - if buddy and service.is_group_service(): - self._handle_new_service_for_group(service, buddy) + if buddy and service.get_activity_uid(): + self._handle_new_service_for_activity(service, buddy) return False @@ -268,8 +279,19 @@ class PresenceService(gobject.GObject): if addr: self._local_addrs[interface] = addr + # Decompose service type if we can + (uid, stype) = Service._decompose_service_type(stype) + # If we care about the service right now, resolve it - if stype in self._allowed_service_types or self._is_special_service_type(stype): + resolve = False + if self._activity_uid and self._activity_uid == uid: + if stype in self._allowed_service_types: + resolve = True + elif not self._activity_uid: + resolve = True + if self._is_special_service_type(stype): + resolve = True + if resolve: gobject.idle_add(self._resolve_service, interface, protocol, name, stype, domain, flags) return False @@ -284,20 +306,23 @@ class PresenceService(gobject.GObject): if not adv_list: return False - # Unresolved services by definition aren't assigned to a buddy + # Get the service object; if none, we have nothing left to do + adv = adv_list[0] + service = adv.service() + if not service: + return False + + # Remove the service from the buddy try: - # Remove the service from the buddy buddy = self._buddies[name] - # FIXME: need to be more careful about how we remove services - # from buddies; this could be spoofed - adv = adv_list[0] - service = adv.get_service() + except KeyError: + pass + else: buddy.remove_service(service) if not buddy.is_valid(): self.emit("buddy-disappeared", buddy) del self._buddies[name] - except KeyError: - pass + self._handle_remove_service_for_activity(service, buddy) return False diff --git a/sugar/presence/Service.py b/sugar/presence/Service.py index 9d21a118..f79f17ad 100644 --- a/sugar/presence/Service.py +++ b/sugar/presence/Service.py @@ -1,5 +1,28 @@ import avahi -import Group +from sugar import util +import string + +def new_group_service(group_name, resource): + """Create a new service suitable for defining a new group.""" + if type(group_name) != type("") or not len(group_name): + raise ValueError("group name must be a valid string.") + if type(resource) != type("") or not len(resource): + raise ValueError("group resource must be a valid string.") + + # Create a randomized service type + data = "%s%s" % (group_name, resource) + stype = "_%s_group_olpc._udp" % sugar.util.unique_id(data) + + properties = {__GROUP_NAME_TAG: group_name, __GROUP_RESOURCE_TAG: resource } + owner_nick = "" + port = random.randint(5000, 65000) + # Use random currently unassigned multicast address + address = "232.%d.%d.%d" % (random.randint(0, 254), random.randint(1, 254), + random.randint(1, 254)) + service = Service.Service(owner_nick, stype, "local", address=address, + port=port, properties=properties) + return service + def _txt_to_dict(txt): """Convert an avahi-returned TXT record formatted @@ -18,6 +41,21 @@ def _txt_to_dict(txt): prop_dict[key] = value return prop_dict +def _decompose_service_type(stype): + """Break a service type into the UID and real service type, if we can.""" + if len(stype) < util.ACTIVITY_UID_LEN + 5: + return (None, stype) + if stype[0] != "_": + return (None, stype) + start = 1 + end = start + util.ACTIVITY_UID_LEN + if stype[end] != "_": + return (None, stype) + uid = stype[start:end] + if not util.validate_activity_uid(uid): + return (None, stype) + return (uid, stype[end:]) + def is_multicast_address(address): """Simple numerical check for whether an IP4 address is in the range for multicast addresses or not.""" @@ -31,12 +69,10 @@ def is_multicast_address(address): return False -__GROUP_UID_TAG = "GroupUID" - 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, group=None): + def __init__(self, name, stype, domain, address=None, port=-1, properties=None): # Validate immutable options if not name or (type(name) != type("") and type(name) != type(u"")) or not len(name): raise ValueError("must specify a valid service name.") @@ -51,15 +87,13 @@ class Service(object): if len(domain) and domain != "local" and domain != u"local": raise ValueError("must use the 'local' domain (for now).") - # Group services must have multicast addresses - if Group.is_group_service_type(stype) and address and not is_multicast_address(address): - raise ValueError("group service type specified, but address was not multicast.") - - if group and not isinstance(group, Group.Group): - raise ValueError("group was not a valid group object.") + (uid, real_stype) = _decompose_service_type(stype) + if uid and not util.validate_activity_uid(activity_uid): + raise ValueError("activity_uid not a valid activity UID.") self._name = name self._stype = stype + self._real_stype = real_stype self._domain = domain self._address = None self.set_address(address) @@ -67,9 +101,7 @@ class Service(object): self.set_port(port) self._properties = {} self.set_properties(properties) - self._group = group - if group: - self._properties[__GROUP_UID_TAG] = group.get_uid() + self._activity_uid = uid def get_name(self): """Return the service's name, usually that of the @@ -81,11 +113,6 @@ class Service(object): False if it is not.""" return is_multicast_address(self._address) - def is_group_service(self): - """Return True if the service represents a Group, - False if it does not.""" - return Group.is_group_service_type(self._stype) - def get_one_property(self, key): """Return one property of the service, or None if the property was not found. Cannot distinguish @@ -131,17 +158,15 @@ class Service(object): raise ValueError("must specify a valid address.") if not len(address): raise ValueError("must specify a valid address.") - if Group.is_group_service_type(self._stype) and not is_multicast_address(address): - raise ValueError("group service type specified, but address was not multicast.") self._address = address def get_domain(self): """Return the ZeroConf/mDNS domain the service was found in.""" return self._domain - def get_group(self): - """Return the group this service is associated with, if any.""" - return self._group + def get_activity_uid(self): + """Return the activity UID this service is associated with, if any.""" + return self._activity_uid ################################################################# diff --git a/sugar/util.py b/sugar/util.py index 9c15edbf..6f704951 100644 --- a/sugar/util.py +++ b/sugar/util.py @@ -19,3 +19,20 @@ def _sha_data(data): def unique_id(data = ''): data_string = "%s%s%s" % (time.time(), random.randint(10000, 100000), data) return _stringify_sha(_sha_data(data_string)) + + +ACTIVITY_UID_LEN = 40 + +def is_hex(s): + return s.strip(string.hexdigits) == '' + +def validate_activity_uid(uid): + """Validate an activity UID.""" + if type(uid) != type(""): + return False + if len(uid) != ACTIVITY_UID_LEN: + return False + if not is_hex(uid): + return False + return True +