Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--shell/Owner.py46
-rw-r--r--shell/PresenceService/PresenceService.py124
-rw-r--r--shell/PresenceService/Service.py119
-rwxr-xr-xshell/Shell.py2
-rw-r--r--sugar/presence/Service.py47
5 files changed, 270 insertions, 68 deletions
diff --git a/shell/Owner.py b/shell/Owner.py
index b659058..b4781f9 100644
--- a/shell/Owner.py
+++ b/shell/Owner.py
@@ -1,9 +1,11 @@
import os
import random
import base64
+import time
import conf
from sugar import env
+import logging
from sugar.p2p import Stream
from sugar.presence import PresenceService
from Friends import Friends
@@ -15,7 +17,7 @@ class ShellOwner(object):
"""Class representing the owner of this machine/instance. This class
runs in the shell and serves up the buddy icon and other stuff. It's the
server portion of the Owner, paired with the client portion in Buddy.py."""
- def __init__(self):
+ def __init__(self, shell):
profile = conf.get_profile()
self._nick = profile.get_nick_name()
@@ -34,6 +36,12 @@ class ShellOwner(object):
self._friends = Friends()
self._invites = Invites()
+ self._shell = shell
+ self._shell.connect('activity-changed', self.__activity_changed_cb)
+ self._last_activity_update = time.time()
+ self._pending_activity_update_timer = None
+ self._pending_activity_update = None
+
def get_friends(self):
return self._friends
@@ -43,10 +51,14 @@ class ShellOwner(object):
def announce(self):
# Create and announce our presence
color = conf.get_profile().get_color()
- props = { 'color': color.to_string() }
+ props = {'color':color.to_string()}
+ activity = self._shell.get_current_activity()
+ if activity is not None:
+ props['cur_activity':activity.get_id()]
+ self._last_activity_update = time.time()
self._service = self._pservice.register_service(self._nick,
PRESENCE_SERVICE_TYPE, properties=props)
- print "Owner '%s' using port %d" % (self._nick, self._service.get_port())
+ logging.debug("Owner '%s' using port %d" % (self._nick, self._service.get_port()))
self._icon_stream = Stream.Stream.new_from_service(self._service)
self._icon_stream.register_reader_handler(self._handle_buddy_icon_request, "get_buddy_icon")
self._icon_stream.register_reader_handler(self._handle_invite, "invite")
@@ -61,3 +73,31 @@ class ShellOwner(object):
"""XMLRPC method, called when the owner is invited to an activity."""
self._invites.add_invite(issuer, bundle_id, activity_id)
return ''
+
+ def __update_advertised_current_activity_cb(self):
+ self._last_activity_update = time.time()
+ self._pending_activity_update_timer = None
+ logging.debug("*** Updating current activity to %s" % self._pending_activity_update)
+ return False
+
+ def __activity_changed_cb(self, shell, activity):
+ """Update our presence service with the latest activity, but no
+ more frequently than every 30 seconds"""
+ self._pending_activity_update = activity.get_id()
+ # If there's no pending update, we must not have updated it in the
+ # last 30 seconds (except for the initial update, hence we also check
+ # for the last update)
+ if not self._pending_activity_update_timer or time.time() - self._last_activity_update > 30:
+ self.__update_advertised_current_activity_cb()
+ return
+
+ # If we have a pending update already, we have nothing left to do
+ if self._pending_activity_update_timer:
+ return
+
+ # Otherwise, we start a timer to update the activity at the next
+ # interval, which should be 30 seconds from the last update, or if that
+ # is in the past already, then now
+ next = 30 - max(30, time.time() - self._last_activity_update)
+ self._pending_activity_update_timer = gobject.timeout_add(next * 1000,
+ self.__update_advertised_current_activity_cb)
diff --git a/shell/PresenceService/PresenceService.py b/shell/PresenceService/PresenceService.py
index 1534ae9..c04084c 100644
--- a/shell/PresenceService/PresenceService.py
+++ b/shell/PresenceService/PresenceService.py
@@ -56,6 +56,38 @@ class ServiceAdv(object):
raise ValueError("Can't reset to resolve pending from resolved.")
self._state = state
+class RegisteredServiceType(object):
+ def __init__(self, stype):
+ self._stype = stype
+ self._refcount = 1
+
+ def get_type(self):
+ return self._stype
+
+ def ref(self):
+ self._refcount += 1
+
+ def unref(self):
+ self._refcount -= 1
+ return self._refcount
+
+def _txt_to_dict(txt):
+ """Convert an avahi-returned TXT record formatted
+ as nested arrays of integers (from dbus) into a dict
+ of key/value string pairs."""
+ prop_dict = {}
+ props = avahi.txt_array_to_string_array(txt)
+ for item in props:
+ key = value = None
+ if '=' not in item:
+ # No = means a boolean value of true
+ key = item
+ value = True
+ else:
+ (key, value) = item.split('=')
+ prop_dict[key] = value
+ return prop_dict
+
_PRESENCE_SERVICE = "org.laptop.Presence"
_PRESENCE_DBUS_INTERFACE = "org.laptop.Presence"
@@ -169,8 +201,9 @@ class PresenceServiceDBusHelper(dbus.service.Object):
return owner.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
- in_signature="os", out_signature="o")
- def joinActivity(self, activity_op, stype):
+ in_signature="os", out_signature="o",
+ sender_keyword="sender")
+ def joinActivity(self, activity_op, stype, sender):
found_activity = None
acts = self._parent.get_activities()
for act in acts:
@@ -179,29 +212,34 @@ class PresenceServiceDBusHelper(dbus.service.Object):
break
if not found_activity:
raise NotFoundError("The activity %s was not found." % activity_op)
- return self._parent.join_activity(found_activity, stype)
+ return self._parent.join_activity(found_activity, stype, sender)
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
- in_signature="ssa{ss}sis", out_signature="o")
- def shareActivity(self, activity_id, stype, properties, address, port, domain):
+ in_signature="ssa{ss}sis", out_signature="o",
+ sender_keyword="sender")
+ def shareActivity(self, activity_id, stype, properties, address, port,
+ domain, sender=None):
if not len(address):
address = None
service = self._parent.share_activity(activity_id, stype, properties, address,
- port, domain)
+ port, domain, sender)
return service.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
- in_signature="ssa{ss}sis", out_signature="o")
- def registerService(self, name, stype, properties, address, port, domain):
+ in_signature="ssa{ss}sis", out_signature="o",
+ sender_keyword="sender")
+ def registerService(self, name, stype, properties, address, port, domain,
+ sender=None):
if not len(address):
address = None
service = self._parent.register_service(name, stype, properties, address,
- port, domain)
+ port, domain, sender)
return service.object_path()
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
- in_signature="o", out_signature="")
- def unregisterService(self, service_op):
+ in_signature="o", out_signature="",
+ sender_keyword="sender")
+ def unregisterService(self, service_op, sender):
found_serv = None
services = self._parent.get_services()
for serv in services:
@@ -210,7 +248,7 @@ class PresenceServiceDBusHelper(dbus.service.Object):
break
if not found_serv:
raise NotFoundError("The activity %s was not found." % service_op)
- return self._parent.unregister_service(found_serv)
+ return self._parent.unregister_service(found_serv, sender)
@dbus.service.method(_PRESENCE_DBUS_INTERFACE,
in_signature="s", out_signature="")
@@ -424,9 +462,10 @@ class PresenceService(object):
key = (full_name, stype)
if not self._services.has_key(key):
objid = self._get_next_object_id()
+ props = _txt_to_dict(txt)
service = Service.Service(self._bus_name, objid, name=full_name,
stype=stype, domain=domain, address=address, port=port,
- properties=txt, source_address=address, local=adv.is_local())
+ properties=props, source_address=address, local=adv.is_local())
self._services[key] = service
else:
# Already tracking this service; likely we were the one that shared it
@@ -588,19 +627,22 @@ class PresenceService(object):
def _new_domain_cb_glue(self, interface, protocol, domain, flags=0):
gobject.idle_add(self._new_domain_cb, interface, protocol, domain, flags)
- def join_activity(self, activity, stype):
+ def join_activity(self, activity, stype, sender):
services = activity.get_services_of_type(stype)
if not len(services):
- raise NotFoundError("The service type %s was not present within the activity %s" % (stype, activity.object_path()))
+ raise NotFoundError("The service type %s was not present within " \
+ "the activity %s" % (stype, activity.object_path()))
act_service = services[0]
props = act_service.get_properties()
color = activity.get_color()
if color:
props['color'] = color
return self._share_activity(activity.get_id(), stype, properties,
- act_service.get_address(), act_service.get_port(), act_service.get_domain())
+ act_service.get_address(), act_service.get_port(),
+ act_service.get_domain(), sender)
- def share_activity(self, activity_id, stype, properties=None, address=None, port=-1, domain=u"local"):
+ def share_activity(self, activity_id, stype, properties=None, address=None,
+ port=-1, domain=u"local", sender=None):
"""Convenience function to share an activity with other buddies."""
if not util.validate_activity_id(activity_id):
raise ValueError("invalid activity id")
@@ -621,14 +663,19 @@ class PresenceService(object):
if color:
properties['color'] = color
- logging.debug('Share activity %s, type %s, address %s, port %d, properties %s' % (activity_id, stype, address, port, properties))
- return self.register_service(real_name, stype, properties, address, port, domain)
+ logging.debug('Share activity %s, type %s, address %s, port %d, " \
+ "properties %s' % (activity_id, stype, address, port,
+ properties))
+ return self.register_service(real_name, stype, properties, address,
+ port, domain, sender)
- def register_service(self, name, stype, properties={}, address=None, port=-1, domain=u"local"):
+ def register_service(self, name, stype, properties={}, address=None,
+ port=-1, domain=u"local", sender=None):
"""Register a new service, advertising it to other Buddies on the network."""
(actid, person_name) = Service.decompose_service_name(name)
if self.get_owner() and person_name != self.get_owner().get_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!")
if not domain or not len(domain):
domain = u"local"
if not port or port == -1:
@@ -647,12 +694,14 @@ class PresenceService(object):
objid = self._get_next_object_id()
service = Service.Service(self._bus_name, objid, name=name,
stype=stype, domain=domain, address=address, port=port,
- properties=properties, source_address=None, local=True)
+ properties=properties, source_address=None, local=True,
+ local_publisher=sender)
self._services[(name, stype)] = service
port = service.get_port()
logging.debug("PS: Will register service with name='%s', stype='%s'," \
- " domain='%s', address='%s', port=%d, info='%s'" % (name, stype, domain, address, port, info))
+ " domain='%s', address='%s', port=%d, info='%s'" % (name, stype,
+ domain, address, port, info))
group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, dbus.String(name),
dbus.String(stype), dbus.String(domain), dbus.String(""), # let Avahi figure the 'host' out
dbus.UInt16(port), info)
@@ -667,7 +716,10 @@ class PresenceService(object):
self.register_service_type(stype)
return service
- def unregister_service(self, service):
+ def unregister_service(self, service, sender=None):
+ local_publisher = service.get_local_publisher()
+ if sender is not None and local_publisher != sender:
+ raise ValueError("Service was not not registered by requesting process!")
group = service.get_avahi_entry_group()
if not group:
raise ValueError("Service was not a local service provided by this laptop!")
@@ -678,9 +730,16 @@ class PresenceService(object):
a certain mDNS service types."""
if type(stype) != type(u""):
raise ValueError("service type must be a unicode string.")
- if stype in self._registered_service_types:
- return
- self._registered_service_types.append(stype)
+
+ # If we've already registered it as a service type, ref it and return
+ for item in self._registered_service_types:
+ if item.get_type() == stype:
+ item.ref()
+ return
+
+ # Otherwise track this type now
+ obj = RegisteredServiceType(stype)
+ self._registered_service_types.append(obj)
# Find unresolved services that match the service type
# we're now interested in, and resolve them
@@ -698,8 +757,15 @@ class PresenceService(object):
"""Stop tracking a certain mDNS service."""
if type(stype) != type(u""):
raise ValueError("service type must be a unicode string.")
- if stype in self._registered_service_types:
- self._registered_service_types.remove(stype)
+ item = None
+ for item in self._registered_service_types:
+ if item.get_type() == stype:
+ break
+ # if it was found, unref it and possibly remove it
+ if item is not None:
+ if item.unref() <= 0:
+ self._registered_service_types.remove(item)
+ del item
def main():
diff --git a/shell/PresenceService/Service.py b/shell/PresenceService/Service.py
index 3c65d99..0c23600 100644
--- a/shell/PresenceService/Service.py
+++ b/shell/PresenceService/Service.py
@@ -5,23 +5,6 @@ from sugar import util
import dbus, dbus.service
import random
-def _txt_to_dict(txt):
- """Convert an avahi-returned TXT record formatted
- as nested arrays of integers (from dbus) into a dict
- of key/value string pairs."""
- prop_dict = {}
- props = avahi.txt_array_to_string_array(txt)
- for item in props:
- key = value = None
- if '=' not in item:
- # No = means a boolean value of true
- key = item
- value = True
- else:
- (key, value) = item.split('=')
- prop_dict[key] = value
- return prop_dict
-
def compose_service_name(name, activity_id):
if type(name) == type(""):
name = unicode(name)
@@ -67,6 +50,11 @@ class ServiceDBusHelper(dbus.service.Object):
self._object_path = object_path
dbus.service.Object.__init__(self, bus_name, self._object_path)
+ @dbus.service.signal(SERVICE_DBUS_INTERFACE,
+ signature="as")
+ def PublishedValueChanged(self, keylist):
+ pass
+
@dbus.service.method(SERVICE_DBUS_INTERFACE,
in_signature="", out_signature="a{sv}")
def getProperties(self):
@@ -90,14 +78,33 @@ class ServiceDBusHelper(dbus.service.Object):
return pary
@dbus.service.method(SERVICE_DBUS_INTERFACE,
- in_signature="s", out_signature="s")
+ in_signature="s")
def getPublishedValue(self, key):
"""Return the value belonging to the requested key from the
service's TXT records."""
- value = self._parent.get_one_property(key)
- if type(value) == type(True):
- value = str(value)
- return value
+ val = self._parent.get_one_property(key)
+ if not val:
+ raise KeyError("Value was not found.")
+ return val
+
+ @dbus.service.method(SERVICE_DBUS_INTERFACE,
+ in_signature="", out_signature="a{sv}")
+ def getPublishedValues(self):
+ pary = {}
+ props = self._parent.get_properties()
+ for key, value in props.items():
+ pary[key] = str(value)
+ return dbus.Dictionary(pary)
+
+ @dbus.service.method(SERVICE_DBUS_INTERFACE,
+ sender_keyword="sender")
+ def setPublishedValue(self, key, value, sender):
+ self._parent.set_property(key, value, sender)
+
+ @dbus.service.method(SERVICE_DBUS_INTERFACE,
+ in_signature="a{sv}", sender_keyword="sender")
+ def setPublishedValues(self, values, sender):
+ self._parent.set_properties(values, sender)
class Service(object):
@@ -105,7 +112,7 @@ class Service(object):
service as advertised on the network."""
def __init__(self, bus_name, object_id, name, stype, domain=u"local",
address=None, port=-1, properties=None, source_address=None,
- local=False):
+ local=False, local_publisher=None):
if not bus_name:
raise ValueError("DBus bus name must be valid")
if not object_id or type(object_id) != type(1):
@@ -137,7 +144,7 @@ class Service(object):
self._port = -1
self.set_port(port)
self._properties = {}
- self.set_properties(properties)
+ self._internal_set_properties(properties, first_time=True)
self._avahi_entry_group = None
self._local = local
@@ -159,13 +166,19 @@ class Service(object):
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))
+ 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
self._owner = None
+ # ID of the D-Bus connection that published this service, if any.
+ # We only let the local publisher modify the service.
+ self._local_publisher = local_publisher
+
# register ourselves with dbus
self._object_id = object_id
self._object_path = SERVICE_DBUS_OBJECT_PATH + str(self._object_id)
@@ -182,6 +195,9 @@ class Service(object):
raise RuntimeError("Can only set a service's owner once")
self._owner = owner
+ def get_local_publisher(self):
+ return self._local_publisher
+
def is_local(self):
return self._local
@@ -207,19 +223,55 @@ class Service(object):
properties."""
return self._properties
- def set_properties(self, properties):
+ def set_property(self, key, value, sender=None):
+ """Set one service property"""
+ if sender is not None and self._local_publisher != sender:
+ raise ValueError("Service was not not registered by requesting process!")
+
+ if type(key) != type(u""):
+ raise ValueError("Key must be a unicode string.")
+ if type(value) != type(u"") or type(value) != type(True):
+ raise ValueError("Key must be a unicode string or a boolean.")
+
+ # Ignore setting the key to it's current value
+ if self._properties.has_key(key):
+ if self._properties[key] == value:
+ return
+
+ # Blank value means remove key
+ remove = False
+ if type(value) == type(u"") and len(value) == 0:
+ remove = True
+ if type(value) == type(False) and value == False:
+ remove = True
+
+ if remove:
+ # If the key wasn't present, return without error
+ if self._properties.has_key(key):
+ del self._properties[key]
+ else:
+ # Otherwise set it
+ if type(value) == type(True):
+ value = ""
+ self._properties[key] = value
+ self._dbus_helper.PublishedValueChanged(key)
+
+ def set_properties(self, properties, sender=None):
+ """Set all service properties in one call"""
+ if sender is not None and self._local_publisher != sender:
+ raise ValueError("Service was not not registered by requesting process!")
+ self._internal_set_properties(properties)
+
+ def _internal_set_properties(self, properties, first_time=False):
"""Set the service's properties from either an Avahi
TXT record (a list of lists of integers), or a
python dictionary."""
+ if type(properties) != type({}):
+ raise ValueError("Properties must be a dictionary.")
self._properties = {}
- props = {}
- if type(properties) == type([]):
- props = _txt_to_dict(properties)
- elif type(properties) == type({}):
- props = properties
# Set key/value pairs on internal property list
- for key, value in props.items():
+ for key, value in properties.items():
if len(key) == 0:
continue
tmp_key = key
@@ -230,6 +282,9 @@ class Service(object):
tmp_val = unicode(tmp_val)
self._properties[tmp_key] = tmp_val
+ if not first_time:
+ self._dbus_helper.PublishedValueChanged(self._properties.keys())
+
def get_type(self):
"""Return the service's service type."""
return self._stype
diff --git a/shell/Shell.py b/shell/Shell.py
index efe6a5a..277e2f9 100755
--- a/shell/Shell.py
+++ b/shell/Shell.py
@@ -73,7 +73,7 @@ class Shell(gobject.GObject):
PresenceService.start()
self._pservice = PresenceService.get_instance()
- self._owner = ShellOwner()
+ self._owner = ShellOwner(self)
self._owner.announce()
self._home_window.set_owner(self._owner)
diff --git a/sugar/presence/Service.py b/sugar/presence/Service.py
index eea7fef..01169e4 100644
--- a/sugar/presence/Service.py
+++ b/sugar/presence/Service.py
@@ -2,11 +2,29 @@ import gobject
import dbus
+def __one_dict_differs(dict1, dict2):
+ for key, value in dict1.items():
+ if not dict2.has_key(key) or dict2[key] != value:
+ return True
+ return False
+
+def __dicts_differ(dict1, dict2):
+ if __one_dict_differs(dict1, dict2):
+ return True
+ if __one_dict_differs(dict2, dict1):
+ return True
+ return False
+
class Service(gobject.GObject):
_PRESENCE_SERVICE = "org.laptop.Presence"
_SERVICE_DBUS_INTERFACE = "org.laptop.Presence.Service"
+ __gsignals__ = {
+ 'published-value-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT]))
+ }
+
def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
gobject.GObject.__init__(self)
self._object_path = object_path
@@ -14,17 +32,40 @@ class Service(gobject.GObject):
self._ps_del_object = del_obj_cb
sobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
self._service = dbus.Interface(sobj, self._SERVICE_DBUS_INTERFACE)
- self._service.connect_to_signal('PropertyChanged', self._property_changed_cb)
+ self._service.connect_to_signal('PropertyChanged', self.__property_changed_cb)
+ self._service.connect_to_signal('PublishedValueChanged',
+ self.__published_value_changed_cb)
self._props = self._service.getProperties()
+ self._pubvals = self._service.getPublishedValues()
def object_path(self):
return self._object_path
- def _property_changed_cb(self, prop_list):
+ def __property_changed_cb(self, prop_list):
self._props = self._service.getProperties()
def get_published_value(self, key):
- return self._service.getPublishedValue(key)
+ return self._pubvals[key]
+
+ def get_published_values(self):
+ self._pubvals = self._service.getPublishedValues()
+
+ def set_published_value(self, key, value):
+ if self._pubvals.has_key(key):
+ if self._pubvals[key] == value:
+ return
+ self._pubvals[key] = value
+ self._service.setPublishedValue(key, value)
+
+ def set_published_values(self, vals):
+ self._service.setPublishedValues(vals)
+ self._pubvals = vals
+
+ def __published_value_changed_cb(self, keys):
+ oldvals = self._pubvals
+ self.get_published_values()
+ if __dicts_differ(oldvals, self._pubvals):
+ self.emit('published-value-changed', keys)
def get_name(self):
return self._props['name']