- Clarify when a service type is supposed to be full/network, and when it's supposed

to be a short one.  Activities should _never_ be exposed to the full/network stype
    since that's an implementation detail of the PresenceService
- Make everything having to do with service objects non-unicode (?) to stop the madness.
    Ideally we want everything to be UTF-8 eventually.
- Fix up PS to deal with service types of short/long variety
- Remove a hack from the Start Page that gets all service advertisements, will fix soon
This commit is contained in:
Dan Williams 2006-06-16 15:27:50 -04:00
parent 38565321d5
commit b31a2176ba
3 changed files with 102 additions and 82 deletions

View File

@ -69,9 +69,7 @@ class PresenceService(gobject.GObject):
'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])), ([gobject.TYPE_PYOBJECT])),
'activity-announced': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 'activity-announced': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
'new-service-adv': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_STRING, gobject.TYPE_STRING]))
} }
__lock = threading.Lock() __lock = threading.Lock()
@ -106,7 +104,8 @@ class PresenceService(gobject.GObject):
self._activity_services = {} 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 = [] # Short service type
self._allowed_activities = [] # activity UID
# Keep track of stuff we're already browsing with ZC # Keep track of stuff we're already browsing with ZC
self._service_type_browsers = {} self._service_type_browsers = {}
@ -115,9 +114,6 @@ class PresenceService(gobject.GObject):
# Resolved service list # Resolved service list
self._service_advs = [] self._service_advs = []
# Main activity UID to filter services on
self._activity_uids = []
self._bus = dbus.SystemBus() self._bus = dbus.SystemBus()
self._server = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, self._server = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME,
avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) 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): 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
(uid, ignore) = service.get_activity_uid() uid = service.get_activity_uid()
if not uid: if not uid:
uid = "*" uid = "*"
if not self._activity_services.has_key(uid): if not self._activity_services.has_key(uid):
@ -222,7 +218,7 @@ class PresenceService(gobject.GObject):
self.emit('activity-announced', service, buddy) self.emit('activity-announced', service, buddy)
def _handle_remove_service_for_activity(self, 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: if not uid:
uid = "*" uid = "*"
if self._activity_services.has_key(uid): if self._activity_services.has_key(uid):
@ -231,28 +227,34 @@ class PresenceService(gobject.GObject):
except: except:
pass 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 """When the service discovery finally gets here, we've got enough information about the
service to assign it to a buddy.""" 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 # If this service was previously unresolved, remove it from the
# unresolved list # unresolved list
adv_list = self._find_service_adv(interface=interface, protocol=protocol, name=name, adv_list = self._find_service_adv(interface=interface, protocol=protocol,
stype=stype, domain=domain) name=name, stype=full_stype, domain=domain)
if not adv_list: if not adv_list:
return False return False
adv = adv_list[0] adv = adv_list[0]
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=name, stype=stype, domain=domain, service = Service.Service(name=name, stype=full_stype, domain=domain,
address=address, port=port, properties=txt) 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)
(uid, ignore) = service.get_activity_uid() uid = service.get_activity_uid()
if buddy and uid: if buddy and uid:
self._handle_new_service_for_activity(service, buddy) self._handle_new_service_for_activity(service, buddy)
@ -272,15 +274,15 @@ class PresenceService(gobject.GObject):
error_handler=self._resolve_service_error_handler) error_handler=self._resolve_service_error_handler)
return False return False
def _service_appeared_cb(self, interface, protocol, name, stype, domain, flags): 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, stype, domain, interface, protocol)) 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 # Add the service to our unresolved services list
adv_list = self._find_service_adv(interface=interface, protocol=protocol, 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: if not adv_list:
adv = ServiceAdv(interface=interface, protocol=protocol, name=name, adv = ServiceAdv(interface=interface, protocol=protocol, name=name.encode(),
stype=stype, domain=domain) stype=full_stype.encode(), domain=domain.encode())
self._service_advs.append(adv) self._service_advs.append(adv)
# Find out the IP address of this interface, if we haven't already # 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 self._local_addrs[interface] = addr
# Decompose service type if we can # Decompose service type if we can
(uid, stype) = Service._decompose_service_type(stype) (uid, short_stype) = Service._decompose_service_type(full_stype.encode())
# FIXME: find a better way of letting StartPage get all activity advertisements
self.emit('new-service-adv', uid, stype)
# If we care about the service right now, resolve it # If we care about the service right now, resolve it
resolve = False resolve = False
if uid in self._activity_uids: if uid in self._allowed_activities:
if stype in self._allowed_service_types: if short_stype in self._allowed_service_types:
resolve = True resolve = True
if self._is_special_service_type(stype): if self._is_special_service_type(short_stype):
resolve = True resolve = True
if resolve: 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 return False
def _service_appeared_cb_glue(self, interface, protocol, name, stype, domain, flags): 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) gobject.idle_add(self._service_appeared_cb, interface, protocol, name, stype, domain, flags)
def _service_disappeared_cb(self, interface, protocol, name, stype, domain, flags): 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, stype, domain, interface, protocol)) 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 # If it's an unresolved service, remove it from our unresolved list
adv_list = self._find_service_adv(interface=interface, protocol=protocol, 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: if not adv_list:
return False return False
@ -390,48 +393,58 @@ class PresenceService(gobject.GObject):
def _new_domain_cb_glue(self, interface, protocol, domain, flags=0): def _new_domain_cb_glue(self, interface, protocol, domain, flags=0):
gobject.idle_add(self._new_domain_cb, interface, protocol, domain, flags) 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 """Requests that the Presence service look for and recognize
a certain mDNS service types.""" a certain mDNS service types."""
if not self._started: if not self._started:
raise RuntimeError("presence service must be started first.") 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.") raise ValueError("service type must be a string.")
if type(stype) == type(u""): if self._is_special_service_type(short_stype):
stype = stype.encode()
if self._is_special_service_type(stype):
return return
if stype in self._allowed_service_types: if short_stype in self._allowed_service_types:
return return
# Decompose service type if we can # Decompose service type if we can
(uid, dec_stype) = Service._decompose_service_type(stype) (uid, dec_stype) = Service._decompose_service_type(short_stype)
if uid and util.validate_activity_uid(uid): if uid:
if uid not in self._activity_uids: raise RuntimeError("Can only track plain service types!")
self._activity_uids.append(uid)
self._allowed_service_types.append(dec_stype) self._allowed_service_types.append(dec_stype)
# Find unresolved services that match the service type # Find unresolved services that match the service type
# we're now interested in, and resolve them # we're now interested in, and resolve them
adv_list = self._find_service_adv(stype=stype) resolv_list = []
for adv in adv_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(), gobject.idle_add(self._resolve_service, adv.interface(),
adv.protocol(), adv.name(), adv.stype(), adv.domain(), 0) 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.""" """Stop tracking a certain mDNS service."""
if not self._started: if not self._started:
raise RuntimeError("presence service must be started first.") 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.") raise ValueError("service type must be a string.")
# Decompose service type if we can # Decompose service type if we can
(uid, stype) = Service._decompose_service_type(stype) (uid, dec_stype) = Service._decompose_service_type(short_stype)
if uid and util.validate_activity_uid(uid): if uid:
if uid in self._activity_uids: raise RuntimeError("Can only untrack plain service types!")
self._activity_uids.remove(uid)
if stype in self._allowed_service_types: if dec_stype in self._allowed_service_types:
self._allowed_service_types.remove(stype) self._allowed_service_types.remove(dec_stype)
def join_shared_activity(self, service): def join_shared_activity(self, service):
"""Convenience function to join a group and notify other buddies """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): def share_activity(self, activity, stype, properties={}, address=None, port=None):
"""Convenience function to share an activity with other buddies.""" """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() uid = activity.get_id()
owner_nick = self._owner.get_nick_name() owner_nick = self._owner.get_nick_name()
real_stype = Service.compose_service_type(stype, uid) 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(""): if address and type(address) != type(""):
raise ValueError("address must be a valid string.") raise ValueError("address must be a valid string.")
if not address: if not address:
@ -470,7 +487,7 @@ class PresenceService(gobject.GObject):
raise RuntimeError("presence service must be started first.") raise RuntimeError("presence service must be started first.")
rs_name = service.get_name() rs_name = service.get_name()
rs_stype = service.get_type() rs_stype = service.get_full_type()
rs_port = service.get_port() rs_port = service.get_port()
if type(rs_port) != type(1) and (rs_port <= 1024 or rs_port > 65536): if type(rs_port) != type(1) and (rs_port <= 1024 or rs_port > 65536):
raise ValueError("invalid service port.") raise ValueError("invalid service port.")
@ -493,7 +510,8 @@ class PresenceService(gobject.GObject):
# should un-register it an re-register with the correct info # should un-register it an re-register with the correct info
if str(exc) == "Local name collision": if str(exc) == "Local name collision":
pass pass
(uid, activity_stype) = service.get_activity_uid() uid = service.get_activity_uid()
activity_stype = service.get_type()
self.track_service_type(activity_stype) self.track_service_type(activity_stype)
return group return group

View File

@ -22,9 +22,12 @@ def _txt_to_dict(txt):
def compose_service_type(stype, activity_uid): def compose_service_type(stype, activity_uid):
if not activity_uid: if not activity_uid:
return stype 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.") 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): def _decompose_service_type(stype):
"""Break a service type into the UID and real service type, if we can.""" """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 """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): def __init__(self, name, stype, domain, address=None, port=-1, properties=None):
full_stype = stype
# Validate immutable options # 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.") 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.") raise ValueError("must specify a service type.")
if not stype.endswith("._tcp") and not stype.endswith("._udp"): if not stype.endswith("._tcp") and not stype.endswith("._udp"):
raise ValueError("must specify a TCP or UDP service type.") 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.") 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).") raise ValueError("must use the 'local' domain (for now).")
if type(stype) == type(u""): (uid, short_stype) = _decompose_service_type(full_stype)
stype = stype.encode()
(uid, real_stype) = _decompose_service_type(stype)
if uid and not util.validate_activity_uid(uid): if uid and not util.validate_activity_uid(uid):
raise ValueError("service type activity uid not a valid activity UID.") raise ValueError("service type activity uid not a valid activity UID.")
if type(name) == type(u""):
name = name.encode()
self._name = name self._name = name
self._stype = stype self._full_stype = full_stype
self._activity_stype = real_stype self._activity_stype = short_stype
if type(domain) == type(u""):
domain = domain.encode()
self._domain = domain self._domain = domain
self._address = None self._address = None
self.set_address(address) self.set_address(address)
@ -143,8 +148,16 @@ class Service(object):
self._properties[key] = self._properties[key].encode() self._properties[key] = self._properties[key].encode()
def get_type(self): def get_type(self):
"""Return the service's service type.""" """Return the service's service type without any activity identifiers."""
return self._stype 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): def get_port(self):
return self._port return self._port
@ -171,10 +184,6 @@ class Service(object):
"""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_activity_uid(self):
"""Return the activity UID this service is associated with, if any."""
return (self._activity_uid, self._activity_stype)
################################################################# #################################################################
# Tests # Tests

View File

@ -20,7 +20,7 @@ class ActivitiesModel(gtk.ListStore):
self.append([ title, address, None, None ]) self.append([ title, address, None, None ])
def add_activity(self, buddy, service): 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')) title = urllib.unquote(service.get_one_property('Title'))
address = urllib.unquote(service.get_one_property('URI')) address = urllib.unquote(service.get_one_property('URI'))
subtitle = 'Shared by %s' % buddy.get_nick_name() subtitle = 'Shared by %s' % buddy.get_nick_name()
@ -75,7 +75,6 @@ class StartPage(gtk.HBox):
self._pservice = PresenceService.get_instance() self._pservice = PresenceService.get_instance()
self._pservice.connect("activity-announced", self._on_activity_announced_cb) 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.start()
self._pservice.track_service_type(BrowserActivity._BROWSER_ACTIVITY_TYPE) self._pservice.track_service_type(BrowserActivity._BROWSER_ACTIVITY_TYPE)
@ -115,12 +114,6 @@ class StartPage(gtk.HBox):
self.pack_start(sw) self.pack_start(sw)
sw.show() 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): def _on_activity_announced_cb(self, pservice, service, buddy):
self._activities.get_model().add_activity(buddy, service) self._activities.get_model().add_activity(buddy, service)