From 89d40971fe520e4d0fc124743773a9f76174da18 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 22 Jun 2006 12:52:30 -0400 Subject: [PATCH 1/6] [hack] show a dialog when there's no available network connection, but at least don't traceback --- shell/StartPage.py | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/shell/StartPage.py b/shell/StartPage.py index 4ceeec33..a73018ac 100644 --- a/shell/StartPage.py +++ b/shell/StartPage.py @@ -6,6 +6,7 @@ import dbus import cgi import xml.sax.saxutils import gobject +import socket from google import google from sugar.presence.PresenceService import PresenceService @@ -26,6 +27,7 @@ class SearchModel(gtk.ListStore): def __init__(self, activities_model, search_text): gtk.ListStore.__init__(self, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) + success = False for row in activities_model: title = row[_COLUMN_TITLE] @@ -34,21 +36,31 @@ class SearchModel(gtk.ListStore): self.append([ title, address, row[_COLUMN_SUBTITLE], row[_COLUMN_SERVICE] ]) google.LICENSE_KEY = '1As9KaJQFHIJ1L0W5EZPl6vBOFvh/Vaf' - data = google.doGoogleSearch(search_text) - - for result in data.results: - title = result.title - - # FIXME what tags should we actually strip? - title = title.replace('', '') - title = title.replace('', '') + try: + data = google.doGoogleSearch(search_text) + success = True + except socket.gaierror, exc: + if exc[0] == -3: # Temporary failure in name resolution + errdlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, + gtk.BUTTONS_OK, "There appears to be no network connection.") + 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 - # unescape these. - title = title.replace('"', '"') - title = title.replace('&', '&') - - self.append([ title, result.URL, None, None ]) + if success == True: + for result in data.results: + title = result.title + + # FIXME what tags should we actually strip? + title = title.replace('', '') + title = title.replace('', '') + + # FIXME I'm sure there is a better way to + # unescape these. + title = title.replace('"', '"') + title = title.replace('&', '&') + + self.append([ title, result.URL, None, None ]) class ActivitiesModel(gtk.ListStore): def __init__(self): From 2351ee045885dec490c8937d9e60d242d4af4ce7 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 22 Jun 2006 12:52:51 -0400 Subject: [PATCH 2/6] [hack] don't traceback when there's no network connection --- sugar/chat/Chat.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sugar/chat/Chat.py b/sugar/chat/Chat.py index 08944288..ec51f2f3 100644 --- a/sugar/chat/Chat.py +++ b/sugar/chat/Chat.py @@ -216,17 +216,21 @@ class Chat(gtk.VBox): def send_sketch(self, svgdata): if not svgdata or not len(svgdata): 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() - self._insert_sketch(owner, svgdata) + if owner: + self._insert_sketch(owner, svgdata) def send_text_message(self, text): """Send a chat message and insert it into the local buffer.""" if len(text) <= 0: 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() - self._insert_rich_message(owner, text) + if owner: + self._insert_rich_message(owner, text) def serialize_message(self, message): owner = PresenceService.get_instance().get_owner() From 2840af85ce3b7d998a356f5606ce0d6637b4092e Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 22 Jun 2006 14:05:38 -0400 Subject: [PATCH 3/6] Stop abusing ZeroConf by overloading the service type field; overload the service name field instead like everyone else does --- activities/browser/BrowserActivity.py | 4 +- shell/StartPage.py | 14 +-- sugar/activity/Activity.py | 2 +- sugar/chat/ActivityChat.py | 2 +- sugar/presence/Buddy.py | 49 ++++---- sugar/presence/PresenceService.py | 160 ++++++++++++-------------- sugar/presence/Service.py | 115 +++++++++--------- sugar/util.py | 12 +- 8 files changed, 165 insertions(+), 193 deletions(-) diff --git a/activities/browser/BrowserActivity.py b/activities/browser/BrowserActivity.py index 2f851dd6..e49cfb4c 100644 --- a/activities/browser/BrowserActivity.py +++ b/activities/browser/BrowserActivity.py @@ -35,7 +35,7 @@ class BrowserActivity(Activity): def _service_appeared_cb(self, pservice, buddy, service): # 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 if service.get_type() == _BROWSER_ACTIVITY_TYPE: @@ -116,7 +116,7 @@ class BrowserActivity(Activity): # Join the shared activity if we were started from one 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) def get_embed(self): diff --git a/shell/StartPage.py b/shell/StartPage.py index a73018ac..ccaa1f02 100644 --- a/shell/StartPage.py +++ b/shell/StartPage.py @@ -19,8 +19,8 @@ _COLUMN_SUBTITLE = 2 _COLUMN_SERVICE = 3 class SearchHelper(object): - def __init__(self, activity_uid): - self.search_uid = activity_uid + def __init__(self, activity_id): + self.search_id = activity_id self.found = False class SearchModel(gtk.ListStore): @@ -74,18 +74,18 @@ class ActivitiesModel(gtk.ListStore): (service, ) = model.get(it, _COLUMN_SERVICE) if not service: return False - if service.get_activity_uid() == helper.search_uid: + if service.get_activity_id() == helper.search_id: helper.found = True return True return False def add_activity(self, buddy, service): # Web Activity check - activity_uid = service.get_activity_uid() - if activity_uid is None: + activity_id = service.get_activity_id() + if activity_id is None: return # Don't show dupes - helper = SearchHelper(activity_uid) + helper = SearchHelper(activity_id) self.foreach(self._filter_dupe_activities, helper) if helper.found == True: return @@ -277,7 +277,7 @@ class StartPage(gtk.HBox): self._activities.set_owner(None) def _on_activity_announced_cb(self, pservice, service, buddy): - print "Found new activity with type %s" % service.get_full_type() + print "Found new activity with type %s" % service.get_type() self._activities_model.add_activity(buddy, service) if self._activities.get_model() != self._activities_model: self._search(self._last_search) diff --git a/sugar/activity/Activity.py b/sugar/activity/Activity.py index 509565c7..2af336a2 100644 --- a/sugar/activity/Activity.py +++ b/sugar/activity/Activity.py @@ -79,7 +79,7 @@ class ActivityDbusService(dbus.service.Object): if service is None: self._activity_id = self._activity_container.add_activity("", self._activity.default_type()) 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._object_path = SHELL_SERVICE_PATH + "/Activities/%s" % self._activity_id diff --git a/sugar/chat/ActivityChat.py b/sugar/chat/ActivityChat.py index 6e3c71c6..f24269bd 100644 --- a/sugar/chat/ActivityChat.py +++ b/sugar/chat/ActivityChat.py @@ -16,7 +16,7 @@ class ActivityChat(GroupChat): self._service_appeared_cb(self._pservice, None, 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: logging.debug('Group chat service appeared, setup the stream.') self._setup_stream(service) diff --git a/sugar/presence/Buddy.py b/sugar/presence/Buddy.py index 65405e88..c4dd0a53 100644 --- a/sugar/presence/Buddy.py +++ b/sugar/presence/Buddy.py @@ -74,27 +74,27 @@ class Buddy(gobject.GObject): if publisher_addr != self._address: logging.error('Service publisher and buddy address doesnt match: %s %s' % (publisher_addr, self._address)) return False - full_type = service.get_full_type() - if full_type in self._services.keys(): + stype = service.get_type() + if stype in self._services.keys(): return False - self._services[full_type] = service + self._services[stype] = service if self._valid: self.emit("service-added", service) # If this is the first service we've seen that's owned by # a particular activity, send out the 'joined-activity' signal - (uid, short_stype) = Service._decompose_service_type(full_type) - if uid is not None: + actid = service.get_activity_id() + if actid is not None: found = False 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 break 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) - if full_type == PRESENCE_SERVICE_TYPE: + if stype == PRESENCE_SERVICE_TYPE: # A buddy isn't valid until its official presence # service has been found and resolved self._valid = True @@ -109,48 +109,41 @@ class Buddy(gobject.GObject): return if service.get_name() != self._nick_name: return - full_type = service.get_full_type() - if self._services.has_key(full_type): + stype = service.get_type() + if self._services.has_key(stype): if self._valid: 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, # and it's just been removed, send out the 'left-actvity' signal - (uid, short_stype) = Service._decompose_service_type(full_type) - if uid is not None: + actid = service.get_activity_id() + if actid is not None: found = False for serv in self._services.values(): - if serv.get_activity_uid() == uid: + if serv.get_activity_id() == actid: found = True break 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) - if full_type == PRESENCE_SERVICE_TYPE: + if stype == PRESENCE_SERVICE_TYPE: self._valid = False def get_service_of_type(self, stype=None, activity=None): """Return a service of a certain type, or None if the buddy doesn't provide that service.""" - short_stype = stype - if not short_stype: + if not stype: 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: - uid = activity.get_id() - if uid is not None: + actid = activity.get_id() 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 - if self._services.has_key(short_stype): - return self._services[short_stype] + if self._services.has_key(stype): + return self._services[stype] return None def is_valid(self): diff --git a/sugar/presence/PresenceService.py b/sugar/presence/PresenceService.py index 276bd69c..c379b856 100644 --- a/sugar/presence/PresenceService.py +++ b/sugar/presence/PresenceService.py @@ -105,7 +105,7 @@ class PresenceService(gobject.GObject): # Our owner object self._owner = None - # activity UID -> Service: services grouped by activity UID + # activity ID -> Service: services grouped by activity ID self._activity_services = {} # All the mdns service types we care about @@ -123,26 +123,21 @@ class PresenceService(gobject.GObject): self._server = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, 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.""" - services = self._find_service_adv(stype = full_stype) + services = self._find_service_adv(stype=stype) if len(services) > 0: return services[0] else: 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.""" - # Decompose service type if we can - (uid, dec_stype) = Service._decompose_service_type(short_stype) - if uid: - raise RuntimeError("Can only track plain service types!") - - uid = activity.get_id() - if self._activity_services.has_key(uid): - services = self._activity_services[uid] + actid = activity.get_id() + if self._activity_services.has_key(actid): + services = self._activity_services[actid] for (buddy, service) in services: - if service.get_type() == short_stype: + if service.get_type() == stype: return service return None @@ -173,7 +168,7 @@ class PresenceService(gobject.GObject): def _resolve_service_error_handler(self, 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.""" adv_list = [] for adv in self._service_advs: @@ -183,13 +178,8 @@ class PresenceService(gobject.GObject): continue if name and adv.name() != name: continue - if is_short_stype: - (uid, dec_stype) = Service._decompose_service_type(adv.stype()) - if uid is None or stype != dec_stype: - continue - else: - if stype and adv.stype() != stype: - continue + if stype and adv.stype() != stype: + continue if domain and adv.domain() != domain: continue adv_list.append(adv) @@ -233,31 +223,31 @@ class PresenceService(gobject.GObject): def _handle_new_service_for_activity(self, service, buddy): # If the serivce is a group service, merge it into our groups list - uid = 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)) + actid = service.get_activity_id() + if not actid: + actid = "*" + if not self._activity_services.has_key(actid): + self._activity_services[actid] = [] + self._activity_services[actid].append((buddy, service)) self.emit('activity-announced', service, buddy) 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): + actid = service.get_activity_id() + if not actid: + actid = "*" + if self._activity_services.has_key(actid): try: self._activity_services.remove((buddy, service)) except: 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 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_stype = full_stype.encode() + full_name = full_name.encode() + stype = stype.encode() domain = domain.encode() host = host.encode() address = address.encode() @@ -265,7 +255,7 @@ class PresenceService(gobject.GObject): # If this service was previously unresolved, remove it from the # unresolved list adv_list = self._find_service_adv(interface=interface, protocol=protocol, - name=name, stype=full_stype, domain=domain) + name=full_name, stype=stype, domain=domain) if not adv_list: return False adv = adv_list[0] @@ -274,14 +264,13 @@ class PresenceService(gobject.GObject): self._resolve_queue.remove(adv) # 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) adv.set_service(service) # Merge the service into our buddy and group lists, if needed buddy = self._handle_new_service_for_buddy(service) - uid = service.get_activity_uid() - if buddy and uid: + if buddy and service.get_activity_id(): self._handle_new_service_for_activity(service, buddy) return False @@ -300,16 +289,16 @@ class PresenceService(gobject.GObject): error_handler=self._resolve_service_error_handler) return False - def _service_appeared_cb(self, interface, protocol, name, full_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)) + 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." % (full_name, flags, stype, domain, interface, protocol)) # Add the service to our unresolved services list 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 if not adv_list: - adv = ServiceAdv(interface=interface, protocol=protocol, name=name.encode(), - stype=full_stype.encode(), domain=domain.encode()) + adv = ServiceAdv(interface=interface, protocol=protocol, name=full_name.encode(), + stype=stype.encode(), domain=domain.encode()) self._service_advs.append(adv) else: adv = adv_list[0] @@ -323,37 +312,37 @@ class PresenceService(gobject.GObject): self._local_addrs[interface] = addr # 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 - 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 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 - if self._is_special_service_type(short_stype): + if self._is_special_service_type(stype): resolve = True if resolve and not adv in self._resolve_queue: self._resolve_queue.append(adv) gobject.idle_add(self._resolve_service, adv) 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 def _service_appeared_cb_glue(self, interface, protocol, name, stype, domain, flags): gobject.idle_add(self._service_appeared_cb, interface, protocol, name, stype, domain, flags) - def _service_disappeared_cb(self, interface, protocol, name, full_stype, domain, flags): - logging.debug("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() + 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." % (full_name, stype, domain, interface, protocol)) + full_name = full_name.encode() + stype = stype.encode() domain = domain.encode() # If it's an unresolved service, remove it from our unresolved list adv_list = self._find_service_adv(interface=interface, protocol=protocol, - name=name, stype=full_stype, domain=domain) + name=full_name, stype=stype, domain=domain) if not adv_list: return False @@ -365,9 +354,12 @@ class PresenceService(gobject.GObject): if not service: return False + # Decompose service type if we can + (actid, buddy_name) = Service._decompose_service_name(full_name) + # Remove the service from the buddy try: - buddy = self._buddies[name] + buddy = self._buddies[buddy_name] except KeyError: pass else: @@ -375,7 +367,7 @@ class PresenceService(gobject.GObject): self.emit('service-disappeared', buddy, service) if not buddy.is_valid(): self.emit("buddy-disappeared", buddy) - del self._buddies[name] + del self._buddies[buddy_name] self._handle_remove_service_for_activity(service, buddy) return False @@ -431,61 +423,48 @@ class PresenceService(gobject.GObject): def _new_domain_cb_glue(self, interface, protocol, domain, flags=0): gobject.idle_add(self._new_domain_cb, interface, protocol, domain, flags) - def track_service_type(self, short_stype): + def track_service_type(self, stype): """Requests that the Presence service look for and recognize a certain mDNS service types.""" if not self._started: raise RuntimeError("presence service must be started first.") - if type(short_stype) == type(u""): + if type(stype) == type(u""): 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.") - if self._is_special_service_type(short_stype): + if self._is_special_service_type(stype): return - if short_stype in self._allowed_service_types: + if stype in self._allowed_service_types: return # Decompose service type if we can - (uid, dec_stype) = Service._decompose_service_type(short_stype) - if uid: - raise RuntimeError("Can only track plain service types!") - self._allowed_service_types.append(dec_stype) - self._check_and_resolve_service_advs(dec_stype) + self._allowed_service_types.append(stype) + self._check_and_resolve_service_advs(stype) - def _check_and_resolve_service_advs(self, short_stype): - """We should only get called with short service types (ie, not - service types that can be decomposed into a UID and a type).""" + def _check_and_resolve_service_advs(self, stype): # Find unresolved services that match the service type # we're now interested in, and resolve them resolv_list = [] - # Find services of this type belonging to specific activities - resolv_list = self._find_service_adv(stype=short_stype, is_short_stype=True) - # And also just plain ones of this type - resolv_list = resolv_list + self._find_service_adv(stype=short_stype) - + # Find services of this type + resolv_list = self._find_service_adv(stype=stype) # Request resolution for them if they aren't in-process already for adv in resolv_list: if adv not in self._resolve_queue: self._resolve_queue.append(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.""" if not self._started: 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.") - if not type(short_stype) == type(""): + if not type(stype) == type(""): raise ValueError("service type must be a string.") - # Decompose service type if we can - (uid, dec_stype) = Service._decompose_service_type(short_stype) - if uid: - raise RuntimeError("Can only untrack plain service types!") - - if dec_stype in self._allowed_service_types: - self._allowed_service_types.remove(dec_stype) + if stype in self._allowed_service_types: + self._allowed_service_types.remove(stype) def join_shared_activity(self, service): """Convenience function to join a group and notify other buddies @@ -498,9 +477,9 @@ class PresenceService(gobject.GObject): """Convenience function to share an activity with other buddies.""" if not self._started: raise RuntimeError("presence service must be started first.") - uid = activity.get_id() + actid = activity.get_id() 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(""): raise ValueError("address must be a valid string.") if address == None: @@ -513,8 +492,8 @@ class PresenceService(gobject.GObject): # random port # port = random.randint(5000, 65535) - logging.debug('Share activity %s, type %s, address %s, port %d, properties %s' % (uid, stype, address, port, properties)) - service = Service.Service(name=owner_nick, stype=real_stype, domain="local", + logging.debug('Share activity %s, type %s, address %s, port %d, properties %s' % (actid, stype, address, port, properties)) + service = Service.Service(name=real_name, stype=stype, domain="local", address=address, port=port, properties=properties) # Publish it to the world self.register_service(service) @@ -528,7 +507,10 @@ class PresenceService(gobject.GObject): rs_name = service.get_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!") - 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_props = service.get_properties() rs_domain = service.get_domain() diff --git a/sugar/presence/Service.py b/sugar/presence/Service.py index 48cc9309..56246bb0 100644 --- a/sugar/presence/Service.py +++ b/sugar/presence/Service.py @@ -19,30 +19,35 @@ def _txt_to_dict(txt): prop_dict[key] = value return prop_dict -def compose_service_type(stype, activity_uid): - if not activity_uid: - return stype - 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.") - composed = "_%s_%s" % (activity_uid, stype) +def compose_service_name(name, activity_id): + if not activity_id: + return name + if type(name) == type(u""): + raise ValueError("name must not be in unicode.") + if not name or type(name) != type(""): + raise ValueError("name must be a valid string.") + composed = "%s [%s]" % (name, activity_id) return composed.encode() -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+1:]) +def _decompose_service_name(name): + """Break a service name into the name and activity ID, if we can.""" + if type(name) != type(""): + raise ValueError("name must be a valid string.") + name_len = len(name) + if name_len < util.ACTIVITY_ID_LEN + 5: + return (None, name) + # check for activity id end marker + if name[name_len - 1] != "]": + return (None, name) + start = name_len - 1 - util.ACTIVITY_ID_LEN + end = name_len - 1 + # check for activity id start marker + 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): """Simple numerical check for whether an IP4 address @@ -61,12 +66,12 @@ def deserialize(sdict): name = sdict['name'] if type(name) == type(u""): name = name.encode() - full_stype = sdict['full_stype'] - if type(full_stype) == type(u""): - full_stype = full_stype.encode() - activity_stype = sdict['activity_stype'] - if type(activity_stype) == type(u""): - activity_stype = activity_stype.encode() + stype = sdict['stype'] + if type(stype) == type(u""): + stype = stype.encode() + activity_id = sdict['activity_id'] + if type(activity_id) == type(u""): + activity_id = activity_id.encode() domain = sdict['domain'] if type(domain) == type(u""): domain = domain.encode() @@ -82,26 +87,26 @@ def deserialize(sdict): address = address.encode() except KeyError: 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) -_ACTIVITY_UID_TAG = "ActivityUID" +_ACTIVITY_ID_TAG = "ActivityID" class Service(object): """Encapsulates information about a specific ZeroConf/mDNS service as advertised on the network.""" def __init__(self, name, stype, domain, address=None, port=-1, properties=None): - full_stype = stype # Validate immutable options if name and type(name) == type(u""): raise ValueError("name must not be in unicode.") if not name or type(name) != type("") or not len(name): raise ValueError("must specify a valid service name.") - if full_stype and type(full_stype) == type(u""): + if stype and type(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): + if not stype or type(stype) != type("") or not len(stype): raise ValueError("must specify a service type.") if not stype.endswith("._tcp") and not stype.endswith("._udp"): raise ValueError("must specify a TCP or UDP service type.") @@ -113,13 +118,9 @@ class Service(object): if len(domain) and domain != "local": raise ValueError("must use the 'local' domain (for now).") - (uid, short_stype) = _decompose_service_type(full_stype) - if uid and not util.validate_activity_uid(uid): - raise ValueError("service type activity uid not a valid activity UID.") - - self._name = name - self._full_stype = full_stype - self._activity_stype = short_stype + (actid, real_name) = _decompose_service_name(name) + self._name = real_name + self._stype = stype self._domain = domain self._port = -1 self.set_port(port) @@ -134,15 +135,15 @@ class Service(object): else: 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 - if self._properties.has_key(_ACTIVITY_UID_TAG): - prop_uid = self._properties[_ACTIVITY_UID_TAG] - if (prop_uid and not uid) or (prop_uid != uid): - raise ValueError("ActivityUID property specified, but the service type's activity UID didn't match it: %s, %s" % (prop_uid, uid)) - self._activity_uid = uid - if uid and not self._properties.has_key(_ACTIVITY_UID_TAG): - self._properties[_ACTIVITY_UID_TAG] = uid + if self._properties.has_key(_ACTIVITY_ID_TAG): + prop_actid = self._properties[_ACTIVITY_ID_TAG] + if (prop_actid and not actid) or (prop_actid != actid): + raise ValueError("ActivityID property specified, but the service names's activity ID didn't match it: %s, %s" % (prop_actid, actid)) + self._activity_id = actid + if actid and not self._properties.has_key(_ACTIVITY_ID_TAG): + self._properties[_ACTIVITY_ID_TAG] = actid def serialize(self, owner=None): sdict = {} @@ -150,8 +151,8 @@ class Service(object): sdict['name'] = dbus.Variant(owner.get_nick_name()) else: sdict['name'] = dbus.Variant(self._name) - sdict['full_stype'] = dbus.Variant(self._full_stype) - sdict['activity_stype'] = dbus.Variant(self._activity_stype) + sdict['stype'] = dbus.Variant(self._stype) + sdict['activity_id'] = dbus.Variant(self._activity_id) sdict['domain'] = dbus.Variant(self._domain) if self._address: sdict['address'] = dbus.Variant(self._address) @@ -207,16 +208,12 @@ class Service(object): self._properties[tmp_key] = tmp_val def get_type(self): - """Return the service's service type without any activity identifiers.""" - return self._activity_stype + """Return the service's service type.""" + return self._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_activity_id(self): + """Return the activity ID this service is associated with, if any.""" + return self._activity_id def get_port(self): return self._port diff --git a/sugar/util.py b/sugar/util.py index 4f83751c..bfddf32b 100644 --- a/sugar/util.py +++ b/sugar/util.py @@ -22,18 +22,18 @@ def unique_id(data = ''): return _stringify_sha(_sha_data(data_string)) -ACTIVITY_UID_LEN = 40 +ACTIVITY_ID_LEN = 40 def is_hex(s): return s.strip(string.hexdigits) == '' -def validate_activity_uid(uid): - """Validate an activity UID.""" - if type(uid) != type("") and type(uid) != type(u""): +def validate_activity_id(actid): + """Validate an activity ID.""" + if type(actid) != type("") and type(actid) != type(u""): return False - if len(uid) != ACTIVITY_UID_LEN: + if len(actid) != ACTIVITY_ID_LEN: return False - if not is_hex(uid): + if not is_hex(actid): return False return True From 735d8bc8b4b84ed12009b2a16af1cd1c38b7e37e Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 22 Jun 2006 14:36:50 -0400 Subject: [PATCH 4/6] rename group_chat -> activity_chat for clarification --- shell/shell.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/shell.py b/shell/shell.py index a86e6dc5..87db70f2 100755 --- a/shell/shell.py +++ b/shell/shell.py @@ -83,10 +83,10 @@ class ActivityHost(dbus.service.Object): notebook.set_current_page(index) def _create_chat(self): - self._group_chat = ActivityChat(self) + self._activity_chat = ActivityChat(self) def get_chat(self): - return self._group_chat + return self._activity_chat def get_default_type(self): return self._default_type @@ -98,7 +98,7 @@ class ActivityHost(dbus.service.Object): pass def publish(self): - self._group_chat.publish() + self._activity_chat.publish() self.peer_service.publish() def tab_close_button_clicked(self, button): From 75402820e326f916b7ba76e6a14c85950b3427e1 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 22 Jun 2006 14:37:05 -0400 Subject: [PATCH 5/6] clarify new activity service message --- shell/StartPage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/StartPage.py b/shell/StartPage.py index ccaa1f02..a43d8e1b 100644 --- a/shell/StartPage.py +++ b/shell/StartPage.py @@ -277,7 +277,7 @@ class StartPage(gtk.HBox): self._activities.set_owner(None) def _on_activity_announced_cb(self, pservice, service, buddy): - print "Found new activity with type %s" % service.get_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) if self._activities.get_model() != self._activities_model: self._search(self._last_search) From be992586b1e0d849a732b44eed7047dd8be62501 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 22 Jun 2006 14:37:34 -0400 Subject: [PATCH 6/6] Don't create more than one activity chat, and join a published chat when it appears --- sugar/chat/ActivityChat.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/sugar/chat/ActivityChat.py b/sugar/chat/ActivityChat.py index f24269bd..a7a65160 100644 --- a/sugar/chat/ActivityChat.py +++ b/sugar/chat/ActivityChat.py @@ -4,23 +4,41 @@ from sugar.chat.GroupChat import GroupChat class ActivityChat(GroupChat): SERVICE_TYPE = "_olpc_activity_chat._udp" - SERVICE_PORT = 6200 def __init__(self, activity): GroupChat.__init__(self) + self._chat_service = None + self._activity = activity self._pservice.connect('service-appeared', self._service_appeared_cb) 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) if service is not None: self._service_appeared_cb(self._pservice, None, service) def _service_appeared_cb(self, pservice, buddy, service): - if service.get_activity_id() == self._activity.get_id(): - if service.get_type() == ActivityChat.SERVICE_TYPE: - logging.debug('Group chat service appeared, setup the stream.') - self._setup_stream(service) + if service.get_activity_id() != self._activity.get_id(): + return + if service.get_type() != ActivityChat.SERVICE_TYPE: + 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): - service = self._pservice.share_activity(self._activity, - stype = ActivityChat.SERVICE_TYPE, port = ActivityChat.SERVICE_PORT) + """Only called when we publish the activity this chat is tied to.""" + self._chat_service = self._pservice.share_activity(self._activity, + stype=ActivityChat.SERVICE_TYPE)