Switch from Groups to grouping services based on activity UID

This commit is contained in:
Dan Williams 2006-06-14 14:42:44 -04:00
parent 55f538253c
commit 1cdaf97f20
4 changed files with 116 additions and 110 deletions

View File

@ -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

View File

@ -2,9 +2,9 @@ import threading
import avahi, dbus, dbus.glib, dbus.dbus_bindings, gobject import avahi, dbus, dbus.glib, dbus.dbus_bindings, gobject
import Buddy import Buddy
import Service import Service
import Group
import os import os
import string
from sugar import util
def _get_local_ip_address(ifname): def _get_local_ip_address(ifname):
"""Call Linux specific bits to retrieve our own IP address.""" """Call Linux specific bits to retrieve our own IP address."""
@ -97,8 +97,8 @@ class PresenceService(gobject.GObject):
# Our owner object # Our owner object
self._owner = None self._owner = None
# group UID -> Group: groups we've found # activity UID -> Service: services grouped by activity UID
self._groups = {} self._activity_services = {}
# All the mdns service types we care about # All the mdns service types we care about
self._allowed_service_types = [] self._allowed_service_types = []
@ -110,7 +110,7 @@ class PresenceService(gobject.GObject):
# Resolved service list # Resolved service list
self._service_advs = [] self._service_advs = []
# Main activity UID to filter on # Main activity UID to filter services on
self._activity_uid = None self._activity_uid = None
self._bus = dbus.SystemBus() self._bus = dbus.SystemBus()
@ -126,8 +126,8 @@ class PresenceService(gobject.GObject):
self._started = True self._started = True
self._lock.release() self._lock.release()
if activity_uid and (not type(activity_uid) == type("") or not len(activity_uid)): if activity_uid and not util.validate_activity_uid(activity_uid):
raise ValueError("activity uid must be a string.") raise ValueError("activity uid must be a valid UID string.")
self._activity_uid = activity_uid self._activity_uid = activity_uid
# Always browse .local # Always browse .local
@ -177,8 +177,6 @@ class PresenceService(gobject.GObject):
type, and False if it's not.""" type, and False if it's not."""
if stype == Buddy.PRESENCE_SERVICE_TYPE: if stype == Buddy.PRESENCE_SERVICE_TYPE:
return True return True
if Group.is_group_service_type(stype):
return True
return False return False
def _handle_new_service_for_buddy(self, service): def _handle_new_service_for_buddy(self, service):
@ -204,12 +202,24 @@ class PresenceService(gobject.GObject):
self.emit("buddy-appeared", buddy) self.emit("buddy-appeared", buddy)
return 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 # If the serivce is a group service, merge it into our groups list
group = None uid = service.get_activity_uid()
if not self._groups.has_key(service.get_type()): if not uid:
group = Group.Group(service) uid = "*"
self._groups[service.get_type()] = group 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): 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 """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) adv.set_resolved(True)
# Update the service now that it's been resolved # 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) adv.set_service(service)
# Merge the service into our buddy and group lists, if needed # Merge the service into our buddy and group lists, if needed
buddy = self._handle_new_service_for_buddy(service) buddy = self._handle_new_service_for_buddy(service)
if buddy and service.is_group_service(): if buddy and service.get_activity_uid():
self._handle_new_service_for_group(service, buddy) self._handle_new_service_for_activity(service, buddy)
return False return False
@ -268,8 +279,19 @@ class PresenceService(gobject.GObject):
if addr: if addr:
self._local_addrs[interface] = 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 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) gobject.idle_add(self._resolve_service, interface, protocol, name, stype, domain, flags)
return False return False
@ -284,20 +306,23 @@ class PresenceService(gobject.GObject):
if not adv_list: if not adv_list:
return False 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: try:
# Remove the service from the buddy
buddy = self._buddies[name] buddy = self._buddies[name]
# FIXME: need to be more careful about how we remove services except KeyError:
# from buddies; this could be spoofed pass
adv = adv_list[0] else:
service = adv.get_service()
buddy.remove_service(service) buddy.remove_service(service)
if not buddy.is_valid(): if not buddy.is_valid():
self.emit("buddy-disappeared", buddy) self.emit("buddy-disappeared", buddy)
del self._buddies[name] del self._buddies[name]
except KeyError: self._handle_remove_service_for_activity(service, buddy)
pass
return False return False

View File

@ -1,5 +1,28 @@
import avahi 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): def _txt_to_dict(txt):
"""Convert an avahi-returned TXT record formatted """Convert an avahi-returned TXT record formatted
@ -18,6 +41,21 @@ def _txt_to_dict(txt):
prop_dict[key] = value prop_dict[key] = value
return prop_dict 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): def is_multicast_address(address):
"""Simple numerical check for whether an IP4 address """Simple numerical check for whether an IP4 address
is in the range for multicast addresses or not.""" is in the range for multicast addresses or not."""
@ -31,12 +69,10 @@ def is_multicast_address(address):
return False return False
__GROUP_UID_TAG = "GroupUID"
class Service(object): class Service(object):
"""Encapsulates information about a specific ZeroConf/mDNS """Encapsulates information about a specific ZeroConf/mDNS
service as advertised on the network.""" 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 # Validate immutable options
if not name or (type(name) != type("") and type(name) != type(u"")) or not len(name): if not name or (type(name) != type("") and type(name) != type(u"")) or not len(name):
raise ValueError("must specify a valid service 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": if len(domain) and domain != "local" and domain != u"local":
raise ValueError("must use the 'local' domain (for now).") raise ValueError("must use the 'local' domain (for now).")
# Group services must have multicast addresses (uid, real_stype) = _decompose_service_type(stype)
if Group.is_group_service_type(stype) and address and not is_multicast_address(address): if uid and not util.validate_activity_uid(activity_uid):
raise ValueError("group service type specified, but address was not multicast.") raise ValueError("activity_uid not a valid activity UID.")
if group and not isinstance(group, Group.Group):
raise ValueError("group was not a valid group object.")
self._name = name self._name = name
self._stype = stype self._stype = stype
self._real_stype = real_stype
self._domain = domain self._domain = domain
self._address = None self._address = None
self.set_address(address) self.set_address(address)
@ -67,9 +101,7 @@ class Service(object):
self.set_port(port) self.set_port(port)
self._properties = {} self._properties = {}
self.set_properties(properties) self.set_properties(properties)
self._group = group self._activity_uid = uid
if group:
self._properties[__GROUP_UID_TAG] = group.get_uid()
def get_name(self): def get_name(self):
"""Return the service's name, usually that of the """Return the service's name, usually that of the
@ -81,11 +113,6 @@ class Service(object):
False if it is not.""" False if it is not."""
return is_multicast_address(self._address) 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): def get_one_property(self, key):
"""Return one property of the service, or None """Return one property of the service, or None
if the property was not found. Cannot distinguish if the property was not found. Cannot distinguish
@ -131,17 +158,15 @@ class Service(object):
raise ValueError("must specify a valid address.") raise ValueError("must specify a valid address.")
if not len(address): if not len(address):
raise ValueError("must specify a valid 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 self._address = address
def get_domain(self): def get_domain(self):
"""Return the ZeroConf/mDNS domain the service was found in.""" """Return the ZeroConf/mDNS domain the service was found in."""
return self._domain return self._domain
def get_group(self): def get_activity_uid(self):
"""Return the group this service is associated with, if any.""" """Return the activity UID this service is associated with, if any."""
return self._group return self._activity_uid
################################################################# #################################################################

View File

@ -19,3 +19,20 @@ def _sha_data(data):
def unique_id(data = ''): def unique_id(data = ''):
data_string = "%s%s%s" % (time.time(), random.randint(10000, 100000), data) data_string = "%s%s%s" % (time.time(), random.randint(10000, 100000), data)
return _stringify_sha(_sha_data(data_string)) 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