Merge branch 'master' of git+ssh://crank.laptop.org/git/sugar

Conflicts:

	sugar/presence/Buddy.py
This commit is contained in:
Marco Pesenti Gritti 2006-06-22 16:01:14 -04:00
commit c234b7b4a3
10 changed files with 224 additions and 219 deletions

View File

@ -35,7 +35,7 @@ class BrowserActivity(Activity):
def _service_appeared_cb(self, pservice, buddy, service): def _service_appeared_cb(self, pservice, buddy, service):
# Make sure the service is for our activity # Make sure the service is for our activity
if service.get_activity_uid() != self._activity_id: if service.get_activity_id() != self._activity_id:
return return
if service.get_type() == _BROWSER_ACTIVITY_TYPE: if service.get_type() == _BROWSER_ACTIVITY_TYPE:
@ -116,7 +116,7 @@ class BrowserActivity(Activity):
# Join the shared activity if we were started from one # Join the shared activity if we were started from one
if self._initial_service: if self._initial_service:
logging.debug("BrowserActivity joining shared activity %s" % self._initial_service.get_activity_uid()) logging.debug("BrowserActivity joining shared activity %s" % self._initial_service.get_activity_id())
self._pservice.join_shared_activity(self._initial_service) self._pservice.join_shared_activity(self._initial_service)
def get_embed(self): def get_embed(self):

View File

@ -6,6 +6,7 @@ import dbus
import cgi import cgi
import xml.sax.saxutils import xml.sax.saxutils
import gobject import gobject
import socket
from google import google from google import google
from sugar.presence.PresenceService import PresenceService from sugar.presence.PresenceService import PresenceService
@ -18,14 +19,15 @@ _COLUMN_SUBTITLE = 2
_COLUMN_SERVICE = 3 _COLUMN_SERVICE = 3
class SearchHelper(object): class SearchHelper(object):
def __init__(self, activity_uid): def __init__(self, activity_id):
self.search_uid = activity_uid self.search_id = activity_id
self.found = False self.found = False
class SearchModel(gtk.ListStore): class SearchModel(gtk.ListStore):
def __init__(self, activities_model, search_text): def __init__(self, activities_model, search_text):
gtk.ListStore.__init__(self, gobject.TYPE_STRING, gobject.TYPE_STRING, gtk.ListStore.__init__(self, gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
success = False
for row in activities_model: for row in activities_model:
title = row[_COLUMN_TITLE] title = row[_COLUMN_TITLE]
@ -34,21 +36,31 @@ class SearchModel(gtk.ListStore):
self.append([ title, address, row[_COLUMN_SUBTITLE], row[_COLUMN_SERVICE] ]) self.append([ title, address, row[_COLUMN_SUBTITLE], row[_COLUMN_SERVICE] ])
google.LICENSE_KEY = '1As9KaJQFHIJ1L0W5EZPl6vBOFvh/Vaf' google.LICENSE_KEY = '1As9KaJQFHIJ1L0W5EZPl6vBOFvh/Vaf'
data = google.doGoogleSearch(search_text) try:
data = google.doGoogleSearch(search_text)
for result in data.results: success = True
title = result.title except socket.gaierror, exc:
if exc[0] == -3: # Temporary failure in name resolution
# FIXME what tags should we actually strip? errdlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO,
title = title.replace('<b>', '') gtk.BUTTONS_OK, "There appears to be no network connection.")
title = title.replace('</b>', '') errdlg.connect("response", lambda d, e: d.destroy())
errdlg.connect("close", lambda d, e: d.destroy())
errdlg.show()
# FIXME I'm sure there is a better way to if success == True:
# unescape these. for result in data.results:
title = title.replace('&quot;', '"') title = result.title
title = title.replace('&amp;', '&')
# FIXME what tags should we actually strip?
self.append([ title, result.URL, None, None ]) title = title.replace('<b>', '')
title = title.replace('</b>', '')
# FIXME I'm sure there is a better way to
# unescape these.
title = title.replace('&quot;', '"')
title = title.replace('&amp;', '&')
self.append([ title, result.URL, None, None ])
class ActivitiesModel(gtk.ListStore): class ActivitiesModel(gtk.ListStore):
def __init__(self): def __init__(self):
@ -62,18 +74,18 @@ class ActivitiesModel(gtk.ListStore):
(service, ) = model.get(it, _COLUMN_SERVICE) (service, ) = model.get(it, _COLUMN_SERVICE)
if not service: if not service:
return False return False
if service.get_activity_uid() == helper.search_uid: if service.get_activity_id() == helper.search_id:
helper.found = True helper.found = True
return True return True
return False return False
def add_activity(self, buddy, service): def add_activity(self, buddy, service):
# Web Activity check # Web Activity check
activity_uid = service.get_activity_uid() activity_id = service.get_activity_id()
if activity_uid is None: if activity_id is None:
return return
# Don't show dupes # Don't show dupes
helper = SearchHelper(activity_uid) helper = SearchHelper(activity_id)
self.foreach(self._filter_dupe_activities, helper) self.foreach(self._filter_dupe_activities, helper)
if helper.found == True: if helper.found == True:
return return
@ -265,7 +277,7 @@ class StartPage(gtk.HBox):
self._activities.set_owner(None) self._activities.set_owner(None)
def _on_activity_announced_cb(self, pservice, service, buddy): def _on_activity_announced_cb(self, pservice, service, buddy):
print "Found new activity with type %s" % service.get_full_type() print "Found new activity service (activity %s of type %s)" % (service.get_activity_id(), service.get_type())
self._activities_model.add_activity(buddy, service) self._activities_model.add_activity(buddy, service)
if self._activities.get_model() != self._activities_model: if self._activities.get_model() != self._activities_model:
self._search(self._last_search) self._search(self._last_search)

View File

@ -83,10 +83,10 @@ class ActivityHost(dbus.service.Object):
notebook.set_current_page(index) notebook.set_current_page(index)
def _create_chat(self): def _create_chat(self):
self._group_chat = ActivityChat(self) self._activity_chat = ActivityChat(self)
def get_chat(self): def get_chat(self):
return self._group_chat return self._activity_chat
def get_default_type(self): def get_default_type(self):
return self._default_type return self._default_type
@ -98,7 +98,7 @@ class ActivityHost(dbus.service.Object):
pass pass
def publish(self): def publish(self):
self._group_chat.publish() self._activity_chat.publish()
self.peer_service.publish() self.peer_service.publish()
def tab_close_button_clicked(self, button): def tab_close_button_clicked(self, button):

View File

@ -79,7 +79,7 @@ class ActivityDbusService(dbus.service.Object):
if service is None: if service is None:
self._activity_id = self._activity_container.add_activity("", self._activity.default_type()) self._activity_id = self._activity_container.add_activity("", self._activity.default_type())
else: else:
self._activity_id = service.get_activity_uid() self._activity_id = service.get_activity_id()
self._activity_container.add_activity_with_id("", self._activity.default_type(), self._activity_id) self._activity_container.add_activity_with_id("", self._activity.default_type(), self._activity_id)
self._object_path = SHELL_SERVICE_PATH + "/Activities/%s" % self._activity_id self._object_path = SHELL_SERVICE_PATH + "/Activities/%s" % self._activity_id

View File

@ -4,23 +4,41 @@ from sugar.chat.GroupChat import GroupChat
class ActivityChat(GroupChat): class ActivityChat(GroupChat):
SERVICE_TYPE = "_olpc_activity_chat._udp" SERVICE_TYPE = "_olpc_activity_chat._udp"
SERVICE_PORT = 6200
def __init__(self, activity): def __init__(self, activity):
GroupChat.__init__(self) GroupChat.__init__(self)
self._chat_service = None
self._activity = activity self._activity = activity
self._pservice.connect('service-appeared', self._service_appeared_cb) self._pservice.connect('service-appeared', self._service_appeared_cb)
self._pservice.track_service_type(ActivityChat.SERVICE_TYPE) self._pservice.track_service_type(ActivityChat.SERVICE_TYPE)
# Find an existing activity chat to latch onto
service = self._pservice.get_activity_service(activity, ActivityChat.SERVICE_TYPE) service = self._pservice.get_activity_service(activity, ActivityChat.SERVICE_TYPE)
if service is not None: if service is not None:
self._service_appeared_cb(self._pservice, None, service) self._service_appeared_cb(self._pservice, None, service)
def _service_appeared_cb(self, pservice, buddy, service): def _service_appeared_cb(self, pservice, buddy, service):
if service.get_activity_uid() == self._activity.get_id(): if service.get_activity_id() != self._activity.get_id():
if service.get_type() == ActivityChat.SERVICE_TYPE: return
logging.debug('Group chat service appeared, setup the stream.') if service.get_type() != ActivityChat.SERVICE_TYPE:
self._setup_stream(service) return
if buddy and buddy.is_owner():
return
if self._chat_service:
return
logging.debug('Activity chat service appeared, setup the stream.')
# Ok, there's an existing chat service that we copy
# parameters and such from
addr = service.get_address()
port = service.get_port()
self._chat_service = self._pservice.share_activity(self._activity,
stype=ActivityChat.SERVICE_TYPE, properties=None,
address=addr, port=port)
self._setup_stream(self._chat_service)
def publish(self): def publish(self):
service = self._pservice.share_activity(self._activity, """Only called when we publish the activity this chat is tied to."""
stype = ActivityChat.SERVICE_TYPE, port = ActivityChat.SERVICE_PORT) self._chat_service = self._pservice.share_activity(self._activity,
stype=ActivityChat.SERVICE_TYPE)

View File

@ -216,17 +216,21 @@ class Chat(gtk.VBox):
def send_sketch(self, svgdata): def send_sketch(self, svgdata):
if not svgdata or not len(svgdata): if not svgdata or not len(svgdata):
return return
self._stream_writer.write(self.serialize_message(svgdata)) if self._stream_writer:
self._stream_writer.write(self.serialize_message(svgdata))
owner = PresenceService.get_instance().get_owner() owner = PresenceService.get_instance().get_owner()
self._insert_sketch(owner, svgdata) if owner:
self._insert_sketch(owner, svgdata)
def send_text_message(self, text): def send_text_message(self, text):
"""Send a chat message and insert it into the local buffer.""" """Send a chat message and insert it into the local buffer."""
if len(text) <= 0: if len(text) <= 0:
return return
self._stream_writer.write(self.serialize_message(text)) if self._stream_writer:
self._stream_writer.write(self.serialize_message(text))
owner = PresenceService.get_instance().get_owner() owner = PresenceService.get_instance().get_owner()
self._insert_rich_message(owner, text) if owner:
self._insert_rich_message(owner, text)
def serialize_message(self, message): def serialize_message(self, message):
owner = PresenceService.get_instance().get_owner() owner = PresenceService.get_instance().get_owner()

View File

@ -74,27 +74,27 @@ class Buddy(gobject.GObject):
if publisher_addr != self._address: if publisher_addr != self._address:
logging.error('Service publisher and buddy address doesnt match: %s %s' % (publisher_addr, self._address)) logging.error('Service publisher and buddy address doesnt match: %s %s' % (publisher_addr, self._address))
return False return False
full_type = service.get_full_type() stype = service.get_type()
if full_type in self._services.keys(): if stype in self._services.keys():
return False return False
self._services[full_type] = service self._services[stype] = service
if self._valid: if self._valid:
self.emit("service-added", service) self.emit("service-added", service)
# If this is the first service we've seen that's owned by # If this is the first service we've seen that's owned by
# a particular activity, send out the 'joined-activity' signal # a particular activity, send out the 'joined-activity' signal
(uid, short_stype) = Service._decompose_service_type(full_type) actid = service.get_activity_id()
if uid is not None: if actid is not None:
found = False found = False
for serv in self._services.values(): for serv in self._services.values():
if serv.get_activity_uid() == uid and serv.get_full_type() != full_type: if serv.get_activity_id() == actid and serv.get_type() != stype:
found = True found = True
break break
if not found: if not found:
print "Buddy (%s) joined activity %s." % (self._nick_name, service.get_activity_uid()) print "Buddy (%s) joined activity %s." % (self._nick_name, actid)
self.emit("joined-activity", service) self.emit("joined-activity", service)
if full_type == PRESENCE_SERVICE_TYPE: if stype == PRESENCE_SERVICE_TYPE:
# A buddy isn't valid until its official presence # A buddy isn't valid until its official presence
# service has been found and resolved # service has been found and resolved
self._valid = True self._valid = True
@ -109,47 +109,39 @@ class Buddy(gobject.GObject):
return return
if service.get_name() != self._nick_name: if service.get_name() != self._nick_name:
return return
full_type = service.get_full_type() stype = service.get_type()
if self._services.has_key(full_type): if self._services.has_key(stype):
if self._valid: if self._valid:
self.emit("service-removed", service) self.emit("service-removed", service)
del self._services[full_type] del self._services[stype]
# If this is the lase service owned by a particular activity, # If this is the lase service owned by a particular activity,
# and it's just been removed, send out the 'left-actvity' signal # and it's just been removed, send out the 'left-actvity' signal
(uid, short_stype) = Service._decompose_service_type(full_type) actid = service.get_activity_id()
if uid is not None: if actid is not None:
found = False found = False
for serv in self._services.values(): for serv in self._services.values():
if serv.get_activity_uid() == uid: if serv.get_activity_id() == actid:
found = True found = True
break break
if not found: if not found:
print "Buddy (%s) left activity %s." % (self._nick_name, service.get_activity_uid()) print "Buddy (%s) left activity %s." % (self._nick_name, actid)
self.emit("left-activity", service) self.emit("left-activity", service)
if full_type == PRESENCE_SERVICE_TYPE: if stype == PRESENCE_SERVICE_TYPE:
self._valid = False self._valid = False
def get_service_of_type(self, stype=None, activity=None): def get_service_of_type(self, stype=None, activity=None):
"""Return a service of a certain type, or None if the buddy """Return a service of a certain type, or None if the buddy
doesn't provide that service.""" doesn't provide that service."""
short_stype = stype if not stype:
if not short_stype:
raise RuntimeError("Need to specify a service type.") raise RuntimeError("Need to specify a service type.")
# Ensure we're only passed short service types
(dec_uid, dec_stype) = Service._decompose_service_type(short_stype)
if dec_uid:
raise RuntimeError("Use plain service types please!")
uid = None
if activity: if activity:
uid = activity.get_id() actid = activity.get_id()
if uid is not None:
for service in self._services.values(): for service in self._services.values():
if service.get_type() == short_stype and service.get_activity_uid() == uid: if service.get_type() == stype and service.get_activity_id() == actid:
return service return service
print self._services.keys()
if self._services.has_key(short_stype): if self._services.has_key(short_stype):
return self._services[short_stype] return self._services[short_stype]
return None return None

View File

@ -104,7 +104,7 @@ class PresenceService(gobject.GObject):
# Our owner object # Our owner object
self._owner = None self._owner = None
# activity UID -> Service: services grouped by activity UID # activity ID -> Service: services grouped by activity ID
self._activity_services = {} self._activity_services = {}
# All the mdns service types we care about # All the mdns service types we care about
@ -122,26 +122,21 @@ class PresenceService(gobject.GObject):
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)
def get_service(self, full_stype): def get_service(self, stype):
"""Find a particular service by full service type.""" """Find a particular service by full service type."""
services = self._find_service_adv(stype = full_stype) services = self._find_service_adv(stype=stype)
if len(services) > 0: if len(services) > 0:
return services[0] return services[0]
else: else:
return None return None
def get_activity_service(self, activity, short_stype): def get_activity_service(self, activity, stype):
"""Find a particular service by activity and service type.""" """Find a particular service by activity and service type."""
# Decompose service type if we can actid = activity.get_id()
(uid, dec_stype) = Service._decompose_service_type(short_stype) if self._activity_services.has_key(actid):
if uid: services = self._activity_services[actid]
raise RuntimeError("Can only track plain service types!")
uid = activity.get_id()
if self._activity_services.has_key(uid):
services = self._activity_services[uid]
for (buddy, service) in services: for (buddy, service) in services:
if service.get_type() == short_stype: if service.get_type() == stype:
return service return service
return None return None
@ -169,7 +164,7 @@ class PresenceService(gobject.GObject):
def _resolve_service_error_handler(self, err): def _resolve_service_error_handler(self, err):
logging.error("error resolving service: %s" % err) logging.error("error resolving service: %s" % err)
def _find_service_adv(self, interface=None, protocol=None, name=None, stype=None, domain=None, is_short_stype=False): def _find_service_adv(self, interface=None, protocol=None, name=None, stype=None, domain=None):
"""Search a list of service advertisements for ones matching certain criteria.""" """Search a list of service advertisements for ones matching certain criteria."""
adv_list = [] adv_list = []
for adv in self._service_advs: for adv in self._service_advs:
@ -179,13 +174,8 @@ class PresenceService(gobject.GObject):
continue continue
if name and adv.name() != name: if name and adv.name() != name:
continue continue
if is_short_stype: if stype and adv.stype() != stype:
(uid, dec_stype) = Service._decompose_service_type(adv.stype()) continue
if uid is None or stype != dec_stype:
continue
else:
if stype and adv.stype() != stype:
continue
if domain and adv.domain() != domain: if domain and adv.domain() != domain:
continue continue
adv_list.append(adv) adv_list.append(adv)
@ -229,31 +219,31 @@ 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 = service.get_activity_uid() actid = service.get_activity_id()
if not uid: if not actid:
uid = "*" actid = "*"
if not self._activity_services.has_key(uid): if not self._activity_services.has_key(actid):
self._activity_services[uid] = [] self._activity_services[actid] = []
self._activity_services[uid].append((buddy, service)) self._activity_services[actid].append((buddy, service))
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 = service.get_activity_uid() actid = service.get_activity_id()
if not uid: if not actid:
uid = "*" actid = "*"
if self._activity_services.has_key(uid): if self._activity_services.has_key(actid):
try: try:
self._activity_services.remove((buddy, service)) self._activity_services.remove((buddy, service))
except: except:
pass pass
def _resolve_service_reply_cb(self, interface, protocol, name, full_stype, domain, host, aprotocol, address, port, txt, flags): def _resolve_service_reply_cb(self, interface, protocol, full_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
service to assign it to a buddy.""" service to assign it to a buddy."""
logging.debug("resolved service '%s' type '%s' domain '%s' to %s:%s" % (name, full_stype, domain, address, port)) logging.debug("resolved service '%s' type '%s' domain '%s' to %s:%s" % (full_name, stype, domain, address, port))
name = name.encode() full_name = full_name.encode()
full_stype = full_stype.encode() stype = stype.encode()
domain = domain.encode() domain = domain.encode()
host = host.encode() host = host.encode()
address = address.encode() address = address.encode()
@ -261,7 +251,7 @@ class PresenceService(gobject.GObject):
# 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, adv_list = self._find_service_adv(interface=interface, protocol=protocol,
name=name, stype=full_stype, domain=domain) name=full_name, stype=stype, domain=domain)
if not adv_list: if not adv_list:
return False return False
adv = adv_list[0] adv = adv_list[0]
@ -270,14 +260,13 @@ class PresenceService(gobject.GObject):
self._resolve_queue.remove(adv) self._resolve_queue.remove(adv)
# Update the service now that it's been resolved # Update the service now that it's been resolved
service = Service.Service(name=name, stype=full_stype, domain=domain, service = Service.Service(name=full_name, stype=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 = service.get_activity_uid() if buddy and service.get_activity_id():
if buddy and uid:
self._handle_new_service_for_activity(service, buddy) self._handle_new_service_for_activity(service, buddy)
return False return False
@ -296,16 +285,16 @@ 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, full_stype, domain, flags): def _service_appeared_cb(self, interface, protocol, full_name, stype, domain, flags):
logging.debug("found service '%s' (%d) of type '%s' in domain '%s' on %i.%i." % (name, flags, full_stype, domain, interface, protocol)) logging.debug("found service '%s' (%d) of type '%s' in domain '%s' on %i.%i." % (full_name, flags, 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.encode(), stype=full_stype.encode(), domain=domain.encode()) name=full_name.encode(), stype=stype.encode(), domain=domain.encode())
adv = None adv = None
if not adv_list: if not adv_list:
adv = ServiceAdv(interface=interface, protocol=protocol, name=name.encode(), adv = ServiceAdv(interface=interface, protocol=protocol, name=full_name.encode(),
stype=full_stype.encode(), domain=domain.encode()) stype=stype.encode(), domain=domain.encode())
self._service_advs.append(adv) self._service_advs.append(adv)
else: else:
adv = adv_list[0] adv = adv_list[0]
@ -319,37 +308,37 @@ 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, short_stype) = Service._decompose_service_type(full_stype.encode()) (actid, buddy_name) = Service._decompose_service_name(full_name.encode())
# FIXME: find a better way of letting the StartPage get everything # FIXME: find a better way of letting the StartPage get everything
self.emit('new-service-adv', uid, short_stype) self.emit('new-service-adv', actid, 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 is not None or short_stype in self._allowed_service_types: if actid is not None or stype in self._allowed_service_types:
resolve = True resolve = True
if self._is_special_service_type(short_stype): if self._is_special_service_type(stype):
resolve = True resolve = True
if resolve and not adv in self._resolve_queue: if resolve and not adv in self._resolve_queue:
self._resolve_queue.append(adv) self._resolve_queue.append(adv)
gobject.idle_add(self._resolve_service, adv) gobject.idle_add(self._resolve_service, adv)
else: else:
logging.debug("Do not resolve service '%s' of type '%s', we don't care about it." % (name, full_stype)) logging.debug("Do not resolve service '%s' of type '%s', we don't care about it." % (full_name, stype))
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, full_stype, domain, flags): def _service_disappeared_cb(self, interface, protocol, full_name, stype, domain, flags):
logging.debug("service '%s' of type '%s' in domain '%s' on %i.%i disappeared." % (name, full_stype, domain, interface, protocol)) logging.debug("service '%s' of type '%s' in domain '%s' on %i.%i disappeared." % (full_name, stype, domain, interface, protocol))
name = name.encode() full_name = full_name.encode()
full_stype = full_stype.encode() stype = stype.encode()
domain = domain.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=full_stype, domain=domain) name=full_name, stype=stype, domain=domain)
if not adv_list: if not adv_list:
return False return False
@ -361,9 +350,12 @@ class PresenceService(gobject.GObject):
if not service: if not service:
return False return False
# Decompose service type if we can
(actid, buddy_name) = Service._decompose_service_name(full_name)
# Remove the service from the buddy # Remove the service from the buddy
try: try:
buddy = self._buddies[name] buddy = self._buddies[buddy_name]
except KeyError: except KeyError:
pass pass
else: else:
@ -371,7 +363,7 @@ class PresenceService(gobject.GObject):
self.emit('service-disappeared', buddy, service) self.emit('service-disappeared', buddy, 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[buddy_name]
self._handle_remove_service_for_activity(service, buddy) self._handle_remove_service_for_activity(service, buddy)
return False return False
@ -427,61 +419,48 @@ 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, short_stype): def track_service_type(self, 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(short_stype) == type(u""): if type(stype) == type(u""):
raise ValueError("service type should not be unicode.") raise ValueError("service type should not be unicode.")
if type(short_stype) != type(""): if type(stype) != type(""):
raise ValueError("service type must be a string.") raise ValueError("service type must be a string.")
if self._is_special_service_type(short_stype): if self._is_special_service_type(stype):
return return
if short_stype in self._allowed_service_types: if 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(short_stype) self._allowed_service_types.append(stype)
if uid: self._check_and_resolve_service_advs(stype)
raise RuntimeError("Can only track plain service types!")
self._allowed_service_types.append(dec_stype)
self._check_and_resolve_service_advs(dec_stype)
def _check_and_resolve_service_advs(self, short_stype): def _check_and_resolve_service_advs(self, stype):
"""We should only get called with short service types (ie, not
service types that can be decomposed into a UID and a type)."""
# 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
resolv_list = [] resolv_list = []
# Find services of this type belonging to specific activities # Find services of this type
resolv_list = self._find_service_adv(stype=short_stype, is_short_stype=True) resolv_list = self._find_service_adv(stype=stype)
# And also just plain ones of this type
resolv_list = resolv_list + self._find_service_adv(stype=short_stype)
# Request resolution for them if they aren't in-process already # Request resolution for them if they aren't in-process already
for adv in resolv_list: for adv in resolv_list:
if adv not in self._resolve_queue: if adv not in self._resolve_queue:
self._resolve_queue.append(adv) self._resolve_queue.append(adv)
gobject.idle_add(self._resolve_service, adv) gobject.idle_add(self._resolve_service, adv)
def untrack_service_type(self, short_stype): def untrack_service_type(self, 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 type(short_stype) == type(u""): if type(stype) == type(u""):
raise ValueError("service type should not be unicode.") raise ValueError("service type should not be unicode.")
if not type(short_stype) == type(""): if not type(stype) == type(""):
raise ValueError("service type must be a string.") raise ValueError("service type must be a string.")
# Decompose service type if we can if stype in self._allowed_service_types:
(uid, dec_stype) = Service._decompose_service_type(short_stype) self._allowed_service_types.remove(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): 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
@ -494,9 +473,9 @@ class PresenceService(gobject.GObject):
"""Convenience function to share an activity with other buddies.""" """Convenience function to share an activity with other buddies."""
if not self._started: if not self._started:
raise RuntimeError("presence service must be started first.") raise RuntimeError("presence service must be started first.")
uid = activity.get_id() actid = 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_name = Service.compose_service_name(owner_nick, actid)
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 address == None: if address == None:
@ -509,8 +488,8 @@ class PresenceService(gobject.GObject):
# random port # # random port #
port = random.randint(5000, 65535) port = random.randint(5000, 65535)
logging.debug('Share activity %s, type %s, address %s, port %d, properties %s' % (uid, stype, address, port, properties)) logging.debug('Share activity %s, type %s, address %s, port %d, properties %s' % (actid, stype, address, port, properties))
service = Service.Service(name=owner_nick, stype=real_stype, domain="local", service = Service.Service(name=real_name, stype=stype, domain="local",
address=address, port=port, properties=properties) address=address, port=port, properties=properties)
# Publish it to the world # Publish it to the world
self.register_service(service) self.register_service(service)
@ -524,7 +503,10 @@ class PresenceService(gobject.GObject):
rs_name = service.get_name() rs_name = service.get_name()
if self.get_owner() and rs_name != self.get_owner().get_nick_name(): if self.get_owner() and rs_name != self.get_owner().get_nick_name():
raise RuntimeError("Tried to register a service that didn't have Owner nick as the service name!") raise RuntimeError("Tried to register a service that didn't have Owner nick as the service name!")
rs_stype = service.get_full_type() actid = service.get_activity_id()
if actid:
rs_name = Service.compose_service_name(rs_name, actid)
rs_stype = service.get_type()
rs_port = service.get_port() rs_port = service.get_port()
rs_props = service.get_properties() rs_props = service.get_properties()
rs_domain = service.get_domain() rs_domain = service.get_domain()

View File

@ -19,30 +19,35 @@ def _txt_to_dict(txt):
prop_dict[key] = value prop_dict[key] = value
return prop_dict return prop_dict
def compose_service_type(stype, activity_uid): def compose_service_name(name, activity_id):
if not activity_uid: if not activity_id:
return stype return name
if type(stype) == type(u""): if type(name) == type(u""):
raise ValueError("stype must not be in unicode.") raise ValueError("name must not be in unicode.")
if not stype or type(stype) != type(""): if not name or type(name) != type(""):
raise ValueError("stype must be a valid string.") raise ValueError("name must be a valid string.")
composed = "_%s_%s" % (activity_uid, stype) composed = "%s [%s]" % (name, activity_id)
return composed.encode() return composed.encode()
def _decompose_service_type(stype): def _decompose_service_name(name):
"""Break a service type into the UID and real service type, if we can.""" """Break a service name into the name and activity ID, if we can."""
if len(stype) < util.ACTIVITY_UID_LEN + 5: if type(name) != type(""):
return (None, stype) raise ValueError("name must be a valid string.")
if stype[0] != "_": name_len = len(name)
return (None, stype) if name_len < util.ACTIVITY_ID_LEN + 5:
start = 1 return (None, name)
end = start + util.ACTIVITY_UID_LEN # check for activity id end marker
if stype[end] != "_": if name[name_len - 1] != "]":
return (None, stype) return (None, name)
uid = stype[start:end] start = name_len - 1 - util.ACTIVITY_ID_LEN
if not util.validate_activity_uid(uid): end = name_len - 1
return (None, stype) # check for activity id start marker
return (uid, stype[end+1:]) if name[start - 1] != "[" or name[start - 2] != " ":
return (None, name)
activity_id = name[start:end]
if not util.validate_activity_id(activity_id):
return (None, name)
return (activity_id, name[:start - 2])
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
@ -61,12 +66,12 @@ def deserialize(sdict):
name = sdict['name'] name = sdict['name']
if type(name) == type(u""): if type(name) == type(u""):
name = name.encode() name = name.encode()
full_stype = sdict['full_stype'] stype = sdict['stype']
if type(full_stype) == type(u""): if type(stype) == type(u""):
full_stype = full_stype.encode() stype = stype.encode()
activity_stype = sdict['activity_stype'] activity_id = sdict['activity_id']
if type(activity_stype) == type(u""): if type(activity_id) == type(u""):
activity_stype = activity_stype.encode() activity_id = activity_id.encode()
domain = sdict['domain'] domain = sdict['domain']
if type(domain) == type(u""): if type(domain) == type(u""):
domain = domain.encode() domain = domain.encode()
@ -82,26 +87,26 @@ def deserialize(sdict):
address = address.encode() address = address.encode()
except KeyError: except KeyError:
pass pass
return Service(name, full_stype, domain, address=address, name = compose_service_name(name, activity_id)
return Service(name, stype, domain, address=address,
port=port, properties=properties) port=port, properties=properties)
_ACTIVITY_UID_TAG = "ActivityUID" _ACTIVITY_ID_TAG = "ActivityID"
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): def __init__(self, name, stype, domain, address=None, port=-1, properties=None):
full_stype = stype
# Validate immutable options # Validate immutable options
if name and type(name) == type(u""): if name and type(name) == type(u""):
raise ValueError("name must not be in unicode.") raise ValueError("name must not be in unicode.")
if not name or type(name) != type("") or not len(name): 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 full_stype and type(full_stype) == type(u""): if stype and type(stype) == type(u""):
raise ValueError("service type must not be in unicode.") raise ValueError("service type must not be in unicode.")
if not full_stype or type(full_stype) != type("") or not len(full_stype): if not stype or type(stype) != type("") or not len(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.")
@ -113,13 +118,9 @@ class Service(object):
if len(domain) and domain != "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).")
(uid, short_stype) = _decompose_service_type(full_stype) (actid, real_name) = _decompose_service_name(name)
if uid and not util.validate_activity_uid(uid): self._name = real_name
raise ValueError("service type activity uid not a valid activity UID.") self._stype = stype
self._name = name
self._full_stype = full_stype
self._activity_stype = short_stype
self._domain = domain self._domain = domain
self._port = -1 self._port = -1
self.set_port(port) self.set_port(port)
@ -134,15 +135,15 @@ class Service(object):
else: else:
self.set_address(address) self.set_address(address)
# Ensure that an ActivityUID tag, if given, matches # Ensure that an ActivityID tag, if given, matches
# what we expect from the service type # what we expect from the service type
if self._properties.has_key(_ACTIVITY_UID_TAG): if self._properties.has_key(_ACTIVITY_ID_TAG):
prop_uid = self._properties[_ACTIVITY_UID_TAG] prop_actid = self._properties[_ACTIVITY_ID_TAG]
if (prop_uid and not uid) or (prop_uid != uid): if (prop_actid and not actid) or (prop_actid != actid):
raise ValueError("ActivityUID property specified, but the service type's activity UID didn't match it: %s, %s" % (prop_uid, uid)) raise ValueError("ActivityID property specified, but the service names's activity ID didn't match it: %s, %s" % (prop_actid, actid))
self._activity_uid = uid self._activity_id = actid
if uid and not self._properties.has_key(_ACTIVITY_UID_TAG): if actid and not self._properties.has_key(_ACTIVITY_ID_TAG):
self._properties[_ACTIVITY_UID_TAG] = uid self._properties[_ACTIVITY_ID_TAG] = actid
def serialize(self, owner=None): def serialize(self, owner=None):
sdict = {} sdict = {}
@ -150,8 +151,8 @@ class Service(object):
sdict['name'] = dbus.Variant(owner.get_nick_name()) sdict['name'] = dbus.Variant(owner.get_nick_name())
else: else:
sdict['name'] = dbus.Variant(self._name) sdict['name'] = dbus.Variant(self._name)
sdict['full_stype'] = dbus.Variant(self._full_stype) sdict['stype'] = dbus.Variant(self._stype)
sdict['activity_stype'] = dbus.Variant(self._activity_stype) sdict['activity_id'] = dbus.Variant(self._activity_id)
sdict['domain'] = dbus.Variant(self._domain) sdict['domain'] = dbus.Variant(self._domain)
if self._address: if self._address:
sdict['address'] = dbus.Variant(self._address) sdict['address'] = dbus.Variant(self._address)
@ -207,16 +208,12 @@ class Service(object):
self._properties[tmp_key] = tmp_val self._properties[tmp_key] = tmp_val
def get_type(self): def get_type(self):
"""Return the service's service type without any activity identifiers.""" """Return the service's service type."""
return self._activity_stype return self._stype
def get_full_type(self): def get_activity_id(self):
"""Return the service's full service type as seen over the network.""" """Return the activity ID this service is associated with, if any."""
return self._full_stype return self._activity_id
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

View File

@ -22,18 +22,18 @@ def unique_id(data = ''):
return _stringify_sha(_sha_data(data_string)) return _stringify_sha(_sha_data(data_string))
ACTIVITY_UID_LEN = 40 ACTIVITY_ID_LEN = 40
def is_hex(s): def is_hex(s):
return s.strip(string.hexdigits) == '' return s.strip(string.hexdigits) == ''
def validate_activity_uid(uid): def validate_activity_id(actid):
"""Validate an activity UID.""" """Validate an activity ID."""
if type(uid) != type("") and type(uid) != type(u""): if type(actid) != type("") and type(actid) != type(u""):
return False return False
if len(uid) != ACTIVITY_UID_LEN: if len(actid) != ACTIVITY_ID_LEN:
return False return False
if not is_hex(uid): if not is_hex(actid):
return False return False
return True return True