diff --git a/cut-n-paste/GoogleSOAPFacade.pyc b/cut-n-paste/GoogleSOAPFacade.pyc index 87cd222a..0734ac99 100644 Binary files a/cut-n-paste/GoogleSOAPFacade.pyc and b/cut-n-paste/GoogleSOAPFacade.pyc differ diff --git a/cut-n-paste/SOAP.pyc b/cut-n-paste/SOAP.pyc index 75e42352..338bf326 100644 Binary files a/cut-n-paste/SOAP.pyc and b/cut-n-paste/SOAP.pyc differ diff --git a/cut-n-paste/google.pyc b/cut-n-paste/google.pyc index 5519ec8b..50dd01f6 100644 Binary files a/cut-n-paste/google.pyc and b/cut-n-paste/google.pyc differ diff --git a/sugar/browser/BrowserActivity.py b/sugar/browser/BrowserActivity.py index b01a8a40..aa06dfb7 100644 --- a/sugar/browser/BrowserActivity.py +++ b/sugar/browser/BrowserActivity.py @@ -1,25 +1,24 @@ -from xml.sax import saxutils - import dbus import pygtk pygtk.require('2.0') import gtk import geckoembed +import urllib from sugar.shell import activity from sugar.browser import NotificationBar from sugar.browser import NavigationToolbar +_BROWSER_ACTIVITY_TYPE = "_web_browser_olpc._udp" + class BrowserActivity(activity.Activity): SOLO = 1 FOLLOWING = 2 LEADING = 3 - def __init__(self, group, uri): + def __init__(self, uri): activity.Activity.__init__(self) - self.uri = uri - self._group = group self._mode = BrowserActivity.SOLO def _update_shared_location(self): @@ -85,14 +84,14 @@ class BrowserActivity(activity.Activity): self._setup_shared(self.uri) def publish(self): - print 'Publish %s' % self.activity_get_id() + print 'Publish %s' % self.get_id() def get_embed(self): return self.embed def share(self): - address = self.embed.get_address() - self._model = self._group.get_store().create_model(address) + url = self.embed.get_address() + self._model = self._group.get_store().create_model(url) self._model.set_value('owner', self._group.get_owner().get_nick_name()) self._update_shared_location() self.set_mode(BrowserActivity.LEADING) @@ -101,8 +100,8 @@ class BrowserActivity(activity.Activity): proxy_obj = bus.get_object('com.redhat.Sugar.Chat', '/com/redhat/Sugar/Chat') chat_shell = dbus.Interface(proxy_obj, 'com.redhat.Sugar.ChatShell') - escaped_title = saxutils.escape(self.embed.get_title()) - escaped_address = saxutils.escape(address) + escaped_title = urllib.quote(self.embed.get_title()) + escaped_url = urllib.quote(url) chat_shell.send_text_message('' + escaped_title + '') diff --git a/sugar/browser/BrowserShell.py b/sugar/browser/BrowserShell.py index fc43fe51..c0864754 100644 --- a/sugar/browser/BrowserShell.py +++ b/sugar/browser/BrowserShell.py @@ -30,6 +30,6 @@ class BrowserShell(dbus.service.Object): @dbus.service.method('com.redhat.Sugar.BrowserShell') def open_browser(self, uri): - browser = BrowserActivity(None, uri) + browser = BrowserActivity(uri) self.__browsers.append(browser) browser.connect_to_shell() diff --git a/sugar/chat/Chat.py b/sugar/chat/Chat.py index 7a80f57f..9b686ef0 100644 --- a/sugar/chat/Chat.py +++ b/sugar/chat/Chat.py @@ -13,7 +13,6 @@ pygtk.require('2.0') import gtk, gobject, pango from sugar.shell import activity -from sugar.presence.Group import Group from sugar.presence import Buddy from sugar.presence.Service import Service from sugar.p2p.Stream import Stream diff --git a/sugar/presence/PresenceService.py b/sugar/presence/PresenceService.py index fd71de8f..8a9d0d05 100644 --- a/sugar/presence/PresenceService.py +++ b/sugar/presence/PresenceService.py @@ -111,13 +111,13 @@ class PresenceService(gobject.GObject): self._service_advs = [] # Main activity UID to filter services on - self._activity_uid = None + self._activity_uids = [] self._bus = dbus.SystemBus() self._server = dbus.Interface(self._bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) - def start(self, activity_uid=None): + def start(self): """Start the presence service by kicking off service discovery.""" self._lock.acquire() if self._started: @@ -126,10 +126,6 @@ class PresenceService(gobject.GObject): self._started = True self._lock.release() - if activity_uid and not util.validate_activity_uid(activity_uid): - raise ValueError("activity uid must be a valid UID string.") - self._activity_uid = activity_uid - # Always browse .local self._new_domain_cb(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "local") @@ -284,11 +280,9 @@ class PresenceService(gobject.GObject): # If we care about the service right now, resolve it resolve = False - if self._activity_uid and self._activity_uid == uid: + if uid in self._activity_uids: if stype in self._allowed_service_types: resolve = True - elif not self._activity_uid: - resolve = True if self._is_special_service_type(stype): resolve = True if resolve: @@ -389,6 +383,11 @@ class PresenceService(gobject.GObject): if stype in self._allowed_service_types: return + # Decompose service type if we can + (uid, stype) = Service._decompose_service_type(stype) + if uid and util.validate_activity_uid(uid): + if uid not in self._activity_uids: + self._activity_uids.append(uid) self._allowed_service_types.append(stype) # Find unresolved services that match the service type @@ -404,24 +403,53 @@ class PresenceService(gobject.GObject): raise RuntimeError("presence service must be started first.") if not type(stype) == type(""): raise ValueError("service type must be a string.") - if name in self._allowed_service_types: + + # Decompose service type if we can + (uid, stype) = Service._decompose_service_type(stype) + if uid and util.validate_activity_uid(uid): + if uid in self._activity_uids: + self._activity_uids.remove(uid) + if stype in self._allowed_service_types: self._allowed_service_types.remove(stype) - def join_group(self, group): + def join_shared_activity(self, service): """Convenience function to join a group and notify other buddies that you are a member of it.""" - if not isinstance(group, Group.Group): - raise ValueError("group was not a valid group.") - gservice = group.get_service() + if not isinstance(service, Service.Service): + raise ValueError("service was not a valid service object.") self.register_service(service) + def share_activity(self, activity, stype, properties={}, address=None, port=None): + """Convenience function to share an activity with other buddies.""" + uid = activity.get_id() + owner_nick = self._owner.get_nick_name() + real_stype = "_%s_%s" % (uid, stype) + if address and type(address) != type(""): + raise ValueError("address must be a valid string.") + if not address: + # Use random currently unassigned multicast address + address = "232.%d.%d.%d" % (random.randint(0, 254), random.randint(1, 254), + random.randint(1, 254)) + + if port and (type(port) != type(1) or port <= 1024 or port >= 65535): + raise ValueError("port must be a number between 1024 and 65535") + if not port: + # random port # + port = random.randint(5000, 65535) + + service = Service.Service(name=owner_nick, stype=real_stype, domain="local", + address=address, port=port, properties=properties) + # Publish it to the world + self.register_service(service) + return service + def register_service(self, service): """Register a new service, advertising it to other Buddies on the network.""" if not self._started: raise RuntimeError("presence service must be started first.") rs_name = service.get_name() - rs_stype = service.get_type() + rs_stype = service.get_network_type() rs_port = service.get_port() if type(rs_port) != type(1) and (rs_port <= 1024 or rs_port > 65536): raise ValueError("invalid service port.") @@ -444,7 +472,7 @@ class PresenceService(gobject.GObject): # should un-register it an re-register with the correct info if str(exc) == "Local name collision": pass - self.track_service_type(rs_stype) + self.track_service_type(service.get_network_type()) return group def get_buddy_by_nick_name(self, nick_name): diff --git a/sugar/presence/Service.py b/sugar/presence/Service.py index f79f17ad..4ad23aa1 100644 --- a/sugar/presence/Service.py +++ b/sugar/presence/Service.py @@ -69,6 +69,8 @@ def is_multicast_address(address): return False +_ACTIVITY_UID_TAG = "ActivityUID" + class Service(object): """Encapsulates information about a specific ZeroConf/mDNS service as advertised on the network.""" @@ -101,7 +103,16 @@ class Service(object): self.set_port(port) self._properties = {} self.set_properties(properties) + + # Ensure that an ActivityUID 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.") self._activity_uid = uid + if uid and not self._properties.has_key(_ACTIVITY_UID_TAG): + self._properties[_ACTIVITY_UID_TAG] = uid def get_name(self): """Return the service's name, usually that of the @@ -141,6 +152,10 @@ class Service(object): """Return the service's service type.""" return self._stype + def get_network_type(self): + """Return the full service type, including activity UID.""" + return self._real_stype + def get_port(self): return self._port diff --git a/sugar/shell/activity.py b/sugar/shell/activity.py index 0e724be2..ce0d537c 100644 --- a/sugar/shell/activity.py +++ b/sugar/shell/activity.py @@ -268,7 +268,7 @@ class Activity(object): else: self._activity_object.set_has_changes(False) - def activity_get_id(self): + def get_id(self): return self._activity_id def shutdown(self):