Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorMike C. Fletcher <mcfletch@raistlin.(none)>2007-04-21 18:05:08 (GMT)
committer Mike C. Fletcher <mcfletch@raistlin.(none)>2007-04-21 18:05:08 (GMT)
commitdf4919de2faaf3ed9719fe66dda54a0e2b2cd0d5 (patch)
tree766a298e2f1bd709775e985a4d586da5ebb8be9a /services
parente571b3bff9b834c099bc82be5e20305d9dfb9730 (diff)
parenta0348a565c09e434514b34b05f3866c97f0aee49 (diff)
Merge branch 'master' of git+ssh://mcfletch@dev.laptop.org/git/sugar
Diffstat (limited to 'services')
-rw-r--r--services/clipboard/clipboardobject.py11
-rw-r--r--services/clipboard/clipboardservice.py3
-rwxr-xr-xservices/clipboard/sugar-clipboard2
-rw-r--r--services/clipboard/typeregistry.py62
-rwxr-xr-xservices/console/sugar-console2
-rwxr-xr-xservices/datastore/sugar-data-store2
-rw-r--r--services/presence/__init__.py36
-rw-r--r--services/presence/activity.py162
-rw-r--r--services/presence/buddy.py343
-rw-r--r--services/presence/presenceservice.py52
-rw-r--r--services/presence/psutils.py6
-rw-r--r--services/presence/server_plugin.py163
-rwxr-xr-xservices/presence/sugar-presence-service2
13 files changed, 756 insertions, 90 deletions
diff --git a/services/clipboard/clipboardobject.py b/services/clipboard/clipboardobject.py
index 919acd0..ab00b14 100644
--- a/services/clipboard/clipboardobject.py
+++ b/services/clipboard/clipboardobject.py
@@ -1,3 +1,6 @@
+import os
+import logging
+
import typeregistry
class ClipboardObject:
@@ -8,6 +11,10 @@ class ClipboardObject:
self._percent = 0
self._formats = {}
+ def destroy(self):
+ for type, format in self._formats.iteritems():
+ format.destroy()
+
def get_id(self):
return self._id
@@ -49,6 +56,10 @@ class Format:
self._data = data
self._on_disk = on_disk
+ def destroy(self):
+ if self._on_disk:
+ os.remove(self._data.replace('file://', ''))
+
def get_type(self):
return self._type
diff --git a/services/clipboard/clipboardservice.py b/services/clipboard/clipboardservice.py
index 0ed423b..16e37b1 100644
--- a/services/clipboard/clipboardservice.py
+++ b/services/clipboard/clipboardservice.py
@@ -109,7 +109,8 @@ class ClipboardDBusServiceHelper(dbus.service.Object):
@dbus.service.method(_CLIPBOARD_DBUS_INTERFACE,
in_signature="o", out_signature="")
def delete_object(self, object_path):
- del self._objects[str(object_path)]
+ cb_object = self._objects.pop(str(object_path))
+ cb_object.destroy()
self.object_deleted(object_path)
logging.debug('Deleted object with object_id ' + object_path)
diff --git a/services/clipboard/sugar-clipboard b/services/clipboard/sugar-clipboard
index 9ee32e7..f9dcfb3 100755
--- a/services/clipboard/sugar-clipboard
+++ b/services/clipboard/sugar-clipboard
@@ -32,7 +32,7 @@ import dbus.glib
from sugar import env
-sys.path.insert(0, env.get_service_path('clipboard'))
+sys.path.append(env.get_service_path('clipboard'))
from clipboardservice import ClipboardService
diff --git a/services/clipboard/typeregistry.py b/services/clipboard/typeregistry.py
index b794cee..0cab0e9 100644
--- a/services/clipboard/typeregistry.py
+++ b/services/clipboard/typeregistry.py
@@ -1,5 +1,7 @@
import logging
from gettext import gettext as _
+import urlparse
+import posixpath
class FileType:
def __init__(self, formats):
@@ -197,6 +199,64 @@ class OOTextFileType(FileType):
return mime_type in cls._types
matches_mime_type = classmethod(matches_mime_type)
+class UriListFileType(FileType):
+
+ _types = set(['text/uri-list'])
+
+ def _is_image(self):
+ uris = self._formats['text/uri-list'].get_data().split('\n')
+ if len(uris) == 1:
+ uri = urlparse.urlparse(uris[0])
+ ext = posixpath.splitext(uri.path)[1]
+ logging.debug(ext)
+ # FIXME: Bad hack, the type registry should treat text/uri-list as a special case.
+ if ext in ['.jpg', '.jpeg', '.gif', '.png', '.svg']:
+ return True
+
+ return False
+
+ def get_name(self):
+ if self._is_image():
+ return _('Image')
+ else:
+ return _('File')
+
+ def get_icon(self):
+ if self._is_image():
+ return 'theme:object-image'
+ else:
+ return 'theme:stock-missing'
+
+ def get_preview(self):
+ return ''
+
+ def get_activity(self):
+ return ''
+
+ def matches_mime_type(cls, mime_type):
+ return mime_type in cls._types
+ matches_mime_type = classmethod(matches_mime_type)
+
+class XoFileType(FileType):
+
+ _types = set(['application/vnd.olpc-x-sugar'])
+
+ def get_name(self):
+ return _('Activity package')
+
+ def get_icon(self):
+ return 'theme:stock-missing'
+
+ def get_preview(self):
+ return ''
+
+ def get_activity(self):
+ return ''
+
+ def matches_mime_type(cls, mime_type):
+ return mime_type in cls._types
+ matches_mime_type = classmethod(matches_mime_type)
+
class UnknownFileType(FileType):
def get_name(self):
return _('Object')
@@ -221,11 +281,13 @@ class TypeRegistry:
self._types.append(MsWordFileType)
self._types.append(RtfFileType)
self._types.append(OOTextFileType)
+ self._types.append(UriListFileType)
self._types.append(UriFileType)
self._types.append(ImageFileType)
self._types.append(AbiwordFileType)
self._types.append(TextFileType)
self._types.append(SqueakProjectFileType)
+ self._types.append(XoFileType)
def get_type(self, formats):
for file_type in self._types:
diff --git a/services/console/sugar-console b/services/console/sugar-console
index d4d7af0..af709a6 100755
--- a/services/console/sugar-console
+++ b/services/console/sugar-console
@@ -7,6 +7,6 @@ import os
import sys
from sugar import env
-sys.path.insert(0, env.get_service_path('console'))
+sys.path.append(env.get_service_path('console'))
import console
diff --git a/services/datastore/sugar-data-store b/services/datastore/sugar-data-store
index 003e273..eb325f2 100755
--- a/services/datastore/sugar-data-store
+++ b/services/datastore/sugar-data-store
@@ -24,7 +24,7 @@ import logging
from sugar import logger
from sugar import env
-sys.path.insert(0, env.get_service_path('datastore'))
+sys.path.append(env.get_service_path('datastore'))
logger.start('data-store')
logging.info('Starting the data store...')
diff --git a/services/presence/__init__.py b/services/presence/__init__.py
index e69de29..bd64375 100644
--- a/services/presence/__init__.py
+++ b/services/presence/__init__.py
@@ -0,0 +1,36 @@
+"""Service to track buddies and activities on the network
+
+Model objects:
+
+ activity.Activity -- tracks a (shared/shareable) activity
+ with many properties and observable events
+
+ buddy.Buddy -- tracks a reference to a particular actor
+ on the network
+
+ buddy.GenericOwner -- actor who owns a particular
+ activity on the network
+
+ buddy.ShellOwner -- actor who owns the local machine
+ connects to the owner module (on the server)
+
+Controller objects:
+
+ presenceservice.PresenceService -- controller which connects
+ a networking plugin to a DBUS service. Generates events
+ for networking events, forwards updates/requests to the
+ server plugin.
+
+ server_plugin.ServerPlugin -- implementation of networking
+ plugin using telepathy Python (Jabber) to provide the
+ underlying communications layer. Generates GObject
+ events that the PresenceService observes to forward onto
+ the DBUS clients.
+
+Utility machinery:
+
+ buddyiconcache.BuddyIconCache -- caches buddy icons on disk
+ based on the "jid" XXX Jabber ID? of the buddy.
+
+ psutils -- trivial function to decode int-list to characters
+"""
diff --git a/services/presence/activity.py b/services/presence/activity.py
index 7471b46..e9c3c05 100644
--- a/services/presence/activity.py
+++ b/services/presence/activity.py
@@ -30,6 +30,9 @@ class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGOb
class Activity(DBusGObject):
+ """Represents a potentially shareable activity on the network.
+ """
+
__gtype_name__ = "Activity"
__gsignals__ = {
@@ -50,6 +53,15 @@ class Activity(DBusGObject):
}
def __init__(self, bus_name, object_id, tp, **kwargs):
+ """Initializes the activity and sets its properties to default values.
+
+ bus_name -- DBUS name for lookup on local host
+ object_id -- The unique worldwide ID for this activity
+ tp -- The server plugin object (stands for "telepathy plugin")
+ kwargs -- Keyword arguments for the GObject properties
+
+ """
+
if not bus_name:
raise ValueError("DBus bus name must be valid")
if not object_id or not isinstance(object_id, int):
@@ -88,6 +100,13 @@ class Activity(DBusGObject):
tp.update_activity_properties(self._id)
def do_get_property(self, pspec):
+ """Gets the value of a property associated with this activity.
+
+ pspec -- Property specifier
+
+ returns The value of the given property.
+ """
+
if pspec.name == "id":
return self._id
elif pspec.name == "name":
@@ -104,6 +123,15 @@ class Activity(DBusGObject):
return self._local
def do_set_property(self, pspec, value):
+ """Sets the value of a property associated with this activity.
+
+ pspec -- Property specifier
+ value -- Desired value
+
+ Note that the "type" property can be set only once; attempting to set it
+ to something different later will raise a RuntimeError.
+
+ """
if pspec.name == "id":
self._id = value
elif pspec.name == "name":
@@ -122,6 +150,15 @@ class Activity(DBusGObject):
self._update_validity()
def _update_validity(self):
+ """Sends a "validity-changed" signal if this activity's validity has changed.
+
+ Determines whether this activity's status has changed from valid to
+ invalid, or invalid to valid, and emits a "validity-changed" signal
+ if either is true. "Valid" means that the object's type, ID, name,
+ colour and type properties have all been set to something valid
+ (i.e., not "None").
+
+ """
try:
old_valid = self._valid
if self._color and self._name and self._id and self._type:
@@ -138,42 +175,80 @@ class Activity(DBusGObject):
@dbus.service.signal(_ACTIVITY_INTERFACE,
signature="o")
def BuddyJoined(self, buddy_path):
+ """Generates DBUS signal when a buddy joins this activity.
+
+ buddy_path -- DBUS path to buddy object
+ """
pass
@dbus.service.signal(_ACTIVITY_INTERFACE,
signature="o")
def BuddyLeft(self, buddy_path):
+ """Generates DBUS signal when a buddy leaves this activity.
+
+ buddy_path -- DBUS path to buddy object
+ """
pass
@dbus.service.signal(_ACTIVITY_INTERFACE,
signature="o")
def NewChannel(self, channel_path):
+ """Generates DBUS signal when a new channel is created for this activity.
+
+ channel_path -- DBUS path to new channel
+
+ XXX - what is this supposed to do? Who is supposed to call it?
+ What is the channel path? Right now this is never called.
+
+ """
pass
# dbus methods
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s")
def GetId(self):
+ """DBUS method to get this activity's ID
+
+ returns Activity ID
+ """
return self.props.id
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s")
def GetColor(self):
+ """DBUS method to get this activity's colour
+
+ returns Activity colour
+ """
return self.props.color
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s")
def GetType(self):
+ """DBUS method to get this activity's type
+
+ returns Activity type
+ """
return self.props.type
@dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="",
async_callbacks=('async_cb', 'async_err_cb'))
def Join(self, async_cb, async_err_cb):
+ """DBUS method to for the local user to attempt to join the activity
+
+ async_cb -- Callback method to be called if join attempt is successful
+ async_err_cb -- Callback method to be called if join attempt is unsuccessful
+
+ """
self.join(async_cb, async_err_cb)
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="ao")
def GetJoinedBuddies(self):
+ """DBUS method to return a list of valid buddies who are joined in this activity
+
+ returns A list of buddy object paths
+ """
ret = []
for buddy in self._buddies:
if buddy.props.valid:
@@ -183,18 +258,37 @@ class Activity(DBusGObject):
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="soao")
def GetChannels(self):
+ """DBUS method to get the list of channels associated with this activity
+
+ returns XXX - Not sure what this returns as get_channels doesn't actually return
+ a list of channels!
+ """
return self.get_channels()
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s")
def GetName(self):
+ """DBUS method to get this activity's name
+
+ returns Activity name
+ """
return self.props.name
# methods
def object_path(self):
+ """Retrieves our dbus.ObjectPath object
+
+ returns DBUS ObjectPath object
+ """
return dbus.ObjectPath(self._object_path)
def get_joined_buddies(self):
+ """Local method to return a list of valid buddies who are joined in this activity
+
+ This method is called by the PresenceService on the local machine.
+
+ returns A list of buddy objects
+ """
ret = []
for buddy in self._buddies:
if buddy.props.valid:
@@ -202,18 +296,40 @@ class Activity(DBusGObject):
return ret
def buddy_joined(self, buddy):
+ """Adds a buddy to this activity and sends a BuddyJoined signal
+
+ buddy -- Buddy object representing the buddy being added
+
+ Adds a buddy to this activity if the buddy is not already in the buddy list.
+ If this activity is "valid", a BuddyJoined signal is also sent.
+ This method is called by the PresenceService on the local machine.
+
+ """
if buddy not in self._buddies:
self._buddies.append(buddy)
if self.props.valid:
self.BuddyJoined(buddy.object_path())
def buddy_left(self, buddy):
+ """Removes a buddy from this activity and sends a BuddyLeft signal.
+
+ buddy -- Buddy object representing the buddy being removed
+
+ Removes a buddy from this activity if the buddy is in the buddy list.
+ If this activity is "valid", a BuddyLeft signal is also sent.
+ This method is called by the PresenceService on the local machine.
+
+ """
if buddy in self._buddies:
self._buddies.remove(buddy)
if self.props.valid:
self.BuddyLeft(buddy.object_path())
def _handle_share_join(self, tp, text_channel):
+ """Called when a join to a network activity was successful.
+
+ Called by the _shared_cb and _joined_cb methods.
+ """
if not text_channel:
logging.debug("Error sharing: text channel was None, shouldn't happen")
raise RuntimeError("Plugin returned invalid text channel")
@@ -225,6 +341,8 @@ class Activity(DBusGObject):
return True
def _shared_cb(self, tp, activity_id, text_channel, exc, userdata):
+ """XXX - not documented yet
+ """
if activity_id != self.props.id:
# Not for us
return
@@ -243,6 +361,11 @@ class Activity(DBusGObject):
logging.debug("Share of activity %s succeeded." % self._id)
def _share(self, (async_cb, async_err_cb), owner):
+ """XXX - not documented yet
+
+ XXX - This method is called externally by the PresenceService despite the fact
+ that this is supposed to be an internal method!
+ """
logging.debug("Starting share of activity %s" % self._id)
if self._joined:
async_err_cb(RuntimeError("Already shared activity %s" % self.props.id))
@@ -252,6 +375,8 @@ class Activity(DBusGObject):
logging.debug("done with share attempt %s" % self._id)
def _joined_cb(self, tp, activity_id, text_channel, exc, userdata):
+ """XXX - not documented yet
+ """
if activity_id != self.props.id:
# Not for us
return
@@ -266,6 +391,16 @@ class Activity(DBusGObject):
async_cb()
def join(self, async_cb, async_err_cb):
+ """Local method for the local user to attempt to join the activity.
+
+ async_cb -- Callback method to be called if join attempt is successful
+ async_err_cb -- Callback method to be called if join attempt is unsuccessful
+
+ The two callbacks are passed to the server_plugin ("tp") object, which in turn
+ passes them back as parameters in a callback to the _joined_cb method; this
+ callback is set up within this method.
+
+ """
if self._joined:
async_err_cb(RuntimeError("Already joined activity %s" % self.props.id))
return
@@ -273,19 +408,35 @@ class Activity(DBusGObject):
self._tp.join_activity(self.props.id, (sigid, async_cb, async_err_cb))
def get_channels(self):
+ """Local method to get the list of channels associated with this activity
+
+ returns XXX - expected a list of channels, instead returning a tuple? ???
+ """
conn = self._tp.get_connection()
# FIXME add tubes and others channels
return str(conn.service_name), conn.object_path, [self._text_channel.object_path]
def leave(self):
+ """Local method called when the user wants to leave the activity.
+
+ (XXX - doesn't appear to be called anywhere!)
+
+ """
if self._joined:
self._text_channel[CHANNEL_INTERFACE].Close()
def _text_channel_closed_cb(self):
+ """Callback method called when the text channel is closed.
+
+ This callback is set up in the _handle_share_join method.
+ """
self._joined = False
self._text_channel = None
def send_properties(self):
+ """Tells the Telepathy server what the properties of this activity are.
+
+ """
props = {}
props['name'] = self._name
props['color'] = self._color
@@ -293,6 +444,17 @@ class Activity(DBusGObject):
self._tp.set_activity_properties(self.props.id, props)
def set_properties(self, properties):
+ """Sets name, colour and/or type properties for this activity all at once.
+
+ properties - Dictionary object containing properties keyed by property names
+
+ Note that if any of the name, colour and/or type property values is changed from
+ what it originally was, the update_validity method will be called, resulting in
+ a "validity-changed" signal being generated. (Also note that unlike with the
+ do_set_property method, it *is* possible to change an already-set activity type
+ to something else; this may be a bug.) Called by the PresenceService on the
+ local machine.
+ """
changed = False
if "name" in properties.keys():
name = properties["name"]
diff --git a/services/presence/buddy.py b/services/presence/buddy.py
index 35191e6..ab91cea 100644
--- a/services/presence/buddy.py
+++ b/services/presence/buddy.py
@@ -1,3 +1,4 @@
+"""An "actor" on the network, whether remote or local"""
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd.
#
@@ -29,6 +30,7 @@ _BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
_OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner"
class NotFoundError(dbus.DBusException):
+ """Raised when a given actor is not found on the network"""
def __init__(self):
dbus.DBusException.__init__(self)
self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'
@@ -37,9 +39,35 @@ class DBusGObjectMetaclass(dbus.service.InterfaceType, gobject.GObjectMeta): pas
class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass
+_PROP_NICK = "nick"
+_PROP_KEY = "key"
+_PROP_ICON = "icon"
+_PROP_CURACT = "current-activity"
+_PROP_COLOR = "color"
+_PROP_OWNER = "owner"
+_PROP_VALID = "valid"
+
class Buddy(DBusGObject):
- """Represents another person on the network and keeps track of the
- activities and resources they make available for sharing."""
+ """Person on the network (tracks properties and shared activites)
+
+ The Buddy is a collection of metadata describing a particular
+ actor/person on the network. The Buddy object tracks a set of
+ activities which the actor has shared with the presence service.
+
+ Buddies have a "valid" property which is used to flag Buddies
+ which are no longer reachable. That is, a Buddy may represent
+ a no-longer reachable target on the network.
+
+ The Buddy emits GObject events that the PresenceService uses
+ to track changes in its status.
+
+ Attributes:
+
+ _activities -- dictionary mapping activity ID to
+ activity.Activity objects
+ handles -- dictionary mapping telepresence client to
+ "handle" (XXX what's that)
+ """
__gsignals__ = {
'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
@@ -51,17 +79,26 @@ class Buddy(DBusGObject):
}
__gproperties__ = {
- 'key' : (str, None, None, None,
+ _PROP_KEY : (str, None, None, None,
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
- 'icon' : (object, None, None, gobject.PARAM_READWRITE),
- 'nick' : (str, None, None, None, gobject.PARAM_READWRITE),
- 'color' : (str, None, None, None, gobject.PARAM_READWRITE),
- 'current-activity' : (str, None, None, None, gobject.PARAM_READWRITE),
- 'valid' : (bool, None, None, False, gobject.PARAM_READABLE),
- 'owner' : (bool, None, None, False, gobject.PARAM_READABLE)
+ _PROP_ICON : (object, None, None, gobject.PARAM_READWRITE),
+ _PROP_NICK : (str, None, None, None, gobject.PARAM_READWRITE),
+ _PROP_COLOR : (str, None, None, None, gobject.PARAM_READWRITE),
+ _PROP_CURACT : (str, None, None, None, gobject.PARAM_READWRITE),
+ _PROP_VALID : (bool, None, None, False, gobject.PARAM_READABLE),
+ _PROP_OWNER : (bool, None, None, False, gobject.PARAM_READABLE)
}
def __init__(self, bus_name, object_id, **kwargs):
+ """Initialize the Buddy object
+
+ bus_name -- DBUS object bus name (identifier)
+ object_id -- the activity's unique identifier
+ kwargs -- used to initialize the object's properties
+
+ constructs a DBUS "object path" from the _BUDDY_PATH
+ and object_id
+ """
if not bus_name:
raise ValueError("DBus bus name must be valid")
if not object_id or not isinstance(object_id, int):
@@ -83,44 +120,62 @@ class Buddy(DBusGObject):
self._nick = None
self._color = None
- if not kwargs.get("key"):
+ if not kwargs.get(_PROP_KEY):
raise ValueError("key required")
+ _ALLOWED_INIT_PROPS = [_PROP_NICK, _PROP_KEY, _PROP_ICON, _PROP_CURACT, _PROP_COLOR]
+ for (key, value) in kwargs.items():
+ if key not in _ALLOWED_INIT_PROPS:
+ logging.debug("Invalid init property '%s'; ignoring..." % key)
+ del kwargs[key]
+
gobject.GObject.__init__(self, **kwargs)
def do_get_property(self, pspec):
- if pspec.name == "key":
+ """Retrieve current value for the given property specifier
+
+ pspec -- property specifier with a "name" attribute
+ """
+ if pspec.name == _PROP_KEY:
return self._key
- elif pspec.name == "icon":
+ elif pspec.name == _PROP_ICON:
return self._icon
- elif pspec.name == "nick":
+ elif pspec.name == _PROP_NICK:
return self._nick
- elif pspec.name == "color":
+ elif pspec.name == _PROP_COLOR:
return self._color
- elif pspec.name == "current-activity":
+ elif pspec.name == _PROP_CURACT:
if not self._current_activity:
return None
if not self._activities.has_key(self._current_activity):
return None
return self._current_activity
- elif pspec.name == "valid":
+ elif pspec.name == _PROP_VALID:
return self._valid
- elif pspec.name == "owner":
+ elif pspec.name == _PROP_OWNER:
return self._owner
def do_set_property(self, pspec, value):
- if pspec.name == "icon":
+ """Set given property
+
+ pspec -- property specifier with a "name" attribute
+ value -- value to set
+
+ emits 'icon-changed' signal on icon setting
+ calls _update_validity on all calls
+ """
+ if pspec.name == _PROP_ICON:
if str(value) != self._icon:
self._icon = str(value)
self.IconChanged(self._icon)
self.emit('icon-changed', self._icon)
- elif pspec.name == "nick":
+ elif pspec.name == _PROP_NICK:
self._nick = value
- elif pspec.name == "color":
+ elif pspec.name == _PROP_COLOR:
self._color = value
- elif pspec.name == "current-activity":
+ elif pspec.name == _PROP_CURACT:
self._current_activity = value
- elif pspec.name == "key":
+ elif pspec.name == _PROP_KEY:
self._key = value
self._update_validity()
@@ -129,27 +184,42 @@ class Buddy(DBusGObject):
@dbus.service.signal(_BUDDY_INTERFACE,
signature="ay")
def IconChanged(self, icon_data):
- pass
+ """Generates DBUS signal with icon_data"""
@dbus.service.signal(_BUDDY_INTERFACE,
signature="o")
def JoinedActivity(self, activity_path):
- pass
+ """Generates DBUS signal when buddy joins activity
+
+ activity_path -- DBUS path to the activity object
+ """
@dbus.service.signal(_BUDDY_INTERFACE,
signature="o")
def LeftActivity(self, activity_path):
- pass
+ """Generates DBUS signal when buddy leaves activity
+
+ activity_path -- DBUS path to the activity object
+ """
@dbus.service.signal(_BUDDY_INTERFACE,
signature="a{sv}")
def PropertyChanged(self, updated):
- pass
+ """Generates DBUS signal when buddy's property changes
+
+ updated -- updated property-set (dictionary) with the
+ Buddy's property (changed) values. Note: not the
+ full set of properties, just the changes.
+ """
# dbus methods
@dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="ay")
def GetIcon(self):
+ """Retrieve Buddy's icon data
+
+ returns empty string or dbus.ByteArray
+ """
if not self.props.icon:
return ""
return dbus.ByteArray(self.props.icon)
@@ -157,6 +227,11 @@ class Buddy(DBusGObject):
@dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="ao")
def GetJoinedActivities(self):
+ """Retrieve set of Buddy's joined activities (paths)
+
+ returns list of dbus service paths for the Buddy's joined
+ activities
+ """
acts = []
for act in self.get_joined_activities():
acts.append(act.object_path())
@@ -165,22 +240,41 @@ class Buddy(DBusGObject):
@dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="a{sv}")
def GetProperties(self):
+ """Retrieve set of Buddy's properties
+
+ returns dictionary of
+ nick : str(nickname)
+ owner : bool( whether this Buddy is an owner??? )
+ XXX what is the owner flag for?
+ key : str(public-key)
+ color: Buddy's icon colour
+ XXX what type?
+ current-activity: Buddy's current activity_id, or
+ "" if no current activity
+ """
props = {}
- props['nick'] = self.props.nick
- props['owner'] = self.props.owner
- props['key'] = self.props.key
- props['color'] = self.props.color
+ props[_PROP_NICK] = self.props.nick
+ props[_PROP_OWNER] = self.props.owner
+ props[_PROP_KEY] = self.props.key
+ props[_PROP_COLOR] = self.props.color
if self.props.current_activity:
- props['current-activity'] = self.props.current_activity
+ props[_PROP_CURACT] = self.props.current_activity
else:
- props['current-activity'] = ""
+ props[_PROP_CURACT] = ""
return props
# methods
def object_path(self):
+ """Retrieve our dbus.ObjectPath object"""
return dbus.ObjectPath(self._object_path)
def add_activity(self, activity):
+ """Add an activity to the Buddy's set of activities
+
+ activity -- activity.Activity instance
+
+ calls JoinedActivity
+ """
actid = activity.props.id
if self._activities.has_key(actid):
return
@@ -189,6 +283,12 @@ class Buddy(DBusGObject):
self.JoinedActivity(activity.object_path())
def remove_activity(self, activity):
+ """Remove the activity from the Buddy's set of activities
+
+ activity -- activity.Activity instance
+
+ calls LeftActivity
+ """
actid = activity.props.id
if not self._activities.has_key(actid):
return
@@ -197,6 +297,7 @@ class Buddy(DBusGObject):
self.LeftActivity(activity.object_path())
def get_joined_activities(self):
+ """Retrieves list of still-valid activity objects"""
acts = []
for act in self._activities.values():
if act.props.valid:
@@ -204,36 +305,54 @@ class Buddy(DBusGObject):
return acts
def set_properties(self, properties):
+ """Set the given set of properties on the object
+
+ properties -- set of property values to set
+
+ if no change, no events generated
+ if change, generates property-changed and
+ calls _update_validity
+ """
changed = False
- if "nick" in properties.keys():
- nick = properties["nick"]
+ changed_props = {}
+ if _PROP_NICK in properties.keys():
+ nick = properties[_PROP_NICK]
if nick != self._nick:
self._nick = nick
+ changed_props[_PROP_NICK] = nick
changed = True
- if "color" in properties.keys():
- color = properties["color"]
+ if _PROP_COLOR in properties.keys():
+ color = properties[_PROP_COLOR]
if color != self._color:
self._color = color
+ changed_props[_PROP_COLOR] = color
changed = True
- if "current-activity" in properties.keys():
- curact = properties["current-activity"]
+ if _PROP_CURACT in properties.keys():
+ curact = properties[_PROP_CURACT]
if curact != self._current_activity:
self._current_activity = curact
+ changed_props[_PROP_CURACT] = curact
changed = True
- if not changed:
+ if not changed or not len(changed_props.keys()):
return
# Try emitting PropertyChanged before updating validity
# to avoid leaking a PropertyChanged signal before the buddy is
# actually valid the first time after creation
if self._valid:
- self.PropertyChanged(properties)
- self.emit('property-changed', properties)
+ self.PropertyChanged(changed_props)
+ self.emit('property-changed', changed_props)
self._update_validity()
def _update_validity(self):
+ """Check whether we are now valid
+
+ validity is True if color, nick and key are non-null
+
+ emits validity-changed if we have changed validity
+ """
try:
old_valid = self._valid
if self._color and self._nick and self._key:
@@ -247,6 +366,13 @@ class Buddy(DBusGObject):
self._valid = False
class GenericOwner(Buddy):
+ """Common functionality for Local User-like objects
+
+ The TestOwner wants to produce something *like* a
+ ShellOwner, but with randomised changes and the like.
+ This class provides the common features for a real
+ local owner and a testing one.
+ """
__gtype_name__ = "GenericOwner"
__gproperties__ = {
@@ -255,7 +381,17 @@ class GenericOwner(Buddy):
'key-hash' : (str, None, None, None, gobject.PARAM_READABLE | gobject.PARAM_CONSTRUCT)
}
- def __init__(self, bus_name, object_id, **kwargs):
+ def __init__(self, ps, bus_name, object_id, **kwargs):
+ """Initialize the GenericOwner instance
+
+ ps -- presenceservice.PresenceService object
+ bus_name -- DBUS object bus name (identifier)
+ object_id -- the activity's unique identifier
+ kwargs -- used to initialize the object's properties
+
+ calls Buddy.__init__
+ """
+ self._ps = ps
self._server = 'olpc.collabora.co.uk'
self._key_hash = None
self._registered = False
@@ -273,28 +409,47 @@ class GenericOwner(Buddy):
self._owner = True
def get_registered(self):
+ """Retrieve whether owner has registered with presence server"""
return self._registered
def get_server(self):
+ """Retrieve presence server (XXX url??)"""
return self._server
def get_key_hash(self):
+ """Retrieve the user's private-key hash"""
return self._key_hash
def set_registered(self, registered):
+ """Customisation point: handle the registration of the owner"""
raise RuntimeError("Subclasses must implement")
class ShellOwner(GenericOwner):
- """Class representing the owner of the machine. This is the client
- portion of the Owner, paired with the server portion in Owner.py."""
-
+ """Representation of the local-machine owner using Sugar's Shell
+
+ The ShellOwner uses the Sugar Shell's dbus services to
+ register for updates about the user's profile description.
+ """
__gtype_name__ = "ShellOwner"
_SHELL_SERVICE = "org.laptop.Shell"
_SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner"
_SHELL_PATH = "/org/laptop/Shell"
- def __init__(self, bus_name, object_id, test=False):
+ def __init__(self, ps, bus_name, object_id, test=False):
+ """Initialize the ShellOwner instance
+
+ ps -- presenceservice.PresenceService object
+ bus_name -- DBUS object bus name (identifier)
+ object_id -- the activity's unique identifier
+ test -- ignored
+
+ Retrieves initial property values from the profile
+ module. Loads the buddy icon from file as well.
+ XXX note: no error handling on that
+
+ calls GenericOwner.__init__
+ """
server = profile.get_server()
key_hash = profile.get_private_key_hash()
registered = profile.get_server_registered()
@@ -307,7 +462,7 @@ class ShellOwner(GenericOwner):
icon = f.read()
f.close()
- GenericOwner.__init__(self, bus_name, object_id, key=key, nick=nick,
+ GenericOwner.__init__(self, ps, bus_name, object_id, key=key, nick=nick,
color=color, icon=icon, server=server, key_hash=key_hash,
registered=registered)
@@ -324,10 +479,17 @@ class ShellOwner(GenericOwner):
pass
def set_registered(self, value):
+ """Handle notification that we have been registered"""
if value:
profile.set_server_registered()
def _name_owner_changed_handler(self, name, old, new):
+ """Handle DBUS notification of a new / renamed service
+
+ Watches for the _SHELL_SERVICE, i.e. the Sugar Shell,
+ and registers with it if we have not yet registered
+ with it (using _connect_to_shell).
+ """
if name != self._SHELL_SERVICE:
return
if (old and len(old)) and (not new and not len(new)):
@@ -338,6 +500,11 @@ class ShellOwner(GenericOwner):
self._connect_to_shell()
def _connect_to_shell(self):
+ """Connect to the Sugar Shell service to watch for events
+
+ Connects the various XChanged events on the Sugar Shell
+ service to our _x_changed_cb methods.
+ """
obj = self._bus.get_object(self._SHELL_SERVICE, self._SHELL_PATH)
self._shell_owner = dbus.Interface(obj, self._SHELL_OWNER_INTERFACE)
self._shell_owner.connect_to_signal('IconChanged', self._icon_changed_cb)
@@ -347,21 +514,30 @@ class ShellOwner(GenericOwner):
self._cur_activity_changed_cb)
def _icon_changed_cb(self, icon):
+ """Handle icon change, set property to generate event"""
self.props.icon = icon
def _color_changed_cb(self, color):
- props = {'color': color}
+ """Handle color change, set property to generate event"""
+ props = {_PROP_COLOR: color}
self.set_properties(props)
def _nick_changed_cb(self, nick):
- props = {'nick': nick}
+ """Handle nickname change, set property to generate event"""
+ props = {_PROP_NICK: nick}
self.set_properties(props)
def _cur_activity_changed_cb(self, activity_id):
+ """Handle current-activity change, set property to generate event
+
+ Filters out local activities (those not in self.activites)
+ because the network users can't join those activities, so
+ the activity_id shared will be None in those cases...
+ """
if not self._activities.has_key(activity_id):
# This activity is local-only
activity_id = None
- props = {'current-activity': activity_id}
+ props = {_PROP_CURACT: activity_id}
self.set_properties(props)
@@ -371,9 +547,12 @@ class TestOwner(GenericOwner):
__gtype_name__ = "TestOwner"
- def __init__(self, bus_name, object_id, test_num):
+ def __init__(self, ps, bus_name, object_id, test_num):
self._cp = ConfigParser()
self._section = "Info"
+ self._test_activities = []
+ self._test_cur_act = ""
+ self._change_timeout = 0
self._cfg_file = os.path.join(env.get_profile_path(), 'test-buddy-%d' % test_num)
@@ -392,11 +571,46 @@ class TestOwner(GenericOwner):
color = xocolor.XoColor().to_string()
icon = _get_random_image()
- GenericOwner.__init__(self, bus_name, object_id, key=pubkey, nick=nick,
+ logging.debug("pubkey is %s" % pubkey)
+ GenericOwner.__init__(self, ps, bus_name, object_id, key=pubkey, nick=nick,
color=color, icon=icon, registered=registered, key_hash=privkey_hash)
+ self._ps.connect('connection-status', self._ps_connection_status_cb)
+
+ def _share_reply_cb(self, actid, object_path):
+ activity = self._ps.internal_get_activity(actid)
+ if not activity or not object_path:
+ logging.debug("Couldn't find activity %s even though it was shared." % actid)
+ return
+ logging.debug("Shared activity %s (%s)." % (actid, activity.props.name))
+ self._test_activities.append(activity)
+
+ def _share_error_cb(self, actid, err):
+ logging.debug("Error sharing activity %s: %s" % (actid, str(err)))
+
+ def _ps_connection_status_cb(self, ps, connected):
+ if not connected:
+ return
+
+ if not len(self._test_activities):
+ # Share some activities
+ actid = util.unique_id("Activity 1")
+ callbacks = (lambda *args: self._share_reply_cb(actid, *args),
+ lambda *args: self._share_error_cb(actid, *args))
+ atype = "org.laptop.WebActivity"
+ properties = {"foo": "bar"}
+ self._ps._share_activity(actid, atype, "Wembley Stadium", properties, callbacks)
+
+ actid2 = util.unique_id("Activity 2")
+ callbacks = (lambda *args: self._share_reply_cb(actid2, *args),
+ lambda *args: self._share_error_cb(actid2, *args))
+ atype = "org.laptop.WebActivity"
+ properties = {"baz": "bar"}
+ self._ps._share_activity(actid2, atype, "Maine Road", properties, callbacks)
+
# Change a random property ever 10 seconds
- gobject.timeout_add(10000, self._update_something)
+ if self._change_timeout == 0:
+ self._change_timeout = gobject.timeout_add(10000, self._update_something)
def set_registered(self, value):
if value:
@@ -437,23 +651,26 @@ class TestOwner(GenericOwner):
self.props.icon = _get_random_image()
elif it == 1:
from sugar.graphics import xocolor
- props = {'color': xocolor.XoColor().to_string()}
+ props = {_PROP_COLOR: xocolor.XoColor().to_string()}
self.set_properties(props)
elif it == 2:
- props = {'nick': _get_random_name()}
+ props = {_PROP_NICK: _get_random_name()}
self.set_properties(props)
elif it == 3:
- bork = random.randint(25, 65)
- it = ""
- for i in range(0, bork):
- it += chr(random.randint(40, 127))
- from sugar import util
- props = {'current-activity': util.unique_id(it)}
+ actid = ""
+ idx = random.randint(0, len(self._test_activities))
+ # if idx == len(self._test_activites), it means no current
+ # activity
+ if idx < len(self._test_activities):
+ activity = self._test_activities[idx]
+ actid = activity.props.id
+ props = {_PROP_CURACT: actid}
self.set_properties(props)
return True
def _hash_private_key(self):
+ """Unused method to has a private key, see profile"""
self.privkey_hash = None
key_path = os.path.join(env.get_profile_path(), 'owner.key')
@@ -504,6 +721,7 @@ def _extract_public_key(keyfile):
return key
def _extract_private_key(keyfile):
+ """Get a private key from a private key file"""
# Extract the private key
try:
f = open(keyfile, "r")
@@ -527,6 +745,7 @@ def _extract_private_key(keyfile):
return key
def _get_new_keypair(num):
+ """Retrieve a public/private key pair for testing"""
# Generate keypair
privkeyfile = os.path.join("/tmp", "test%d.key" % num)
pubkeyfile = os.path.join("/tmp", 'test%d.key.pub' % num)
@@ -559,10 +778,12 @@ def _get_new_keypair(num):
return (pubkey, privkey)
def _get_random_name():
+ """Produce random names for testing"""
names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"]
return names[random.randint(0, len(names) - 1)]
def _get_random_image():
+ """Produce a random image for display"""
import cairo, math, random, gtk
def rand():
diff --git a/services/presence/presenceservice.py b/services/presence/presenceservice.py
index fd4cbb6..1312fec 100644
--- a/services/presence/presenceservice.py
+++ b/services/presence/presenceservice.py
@@ -40,24 +40,36 @@ class NotFoundError(dbus.DBusException):
dbus.DBusException.__init__(self)
self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'
+class DBusGObjectMetaclass(dbus.service.InterfaceType, gobject.GObjectMeta): pass
+class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass
+
+class PresenceService(DBusGObject):
+ __gtype_name__ = "PresenceService"
+
+ __gsignals__ = {
+ 'connection-status': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_BOOLEAN]))
+ }
-class PresenceService(dbus.service.Object):
def __init__(self, test=0):
self._next_object_id = 0
+ self._connected = False
self._buddies = {} # key -> Buddy
self._handles_buddies = {} # tp client -> (handle -> Buddy)
self._activities = {} # activity id -> Activity
+ gobject.GObject.__init__(self)
+
bus = dbus.SessionBus()
self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, bus=bus)
# Create the Owner object
objid = self._get_next_object_id()
if test > 0:
- self._owner = TestOwner(self._bus_name, objid, test)
+ self._owner = TestOwner(self, self._bus_name, objid, test)
else:
- self._owner = ShellOwner(self._bus_name, objid)
+ self._owner = ShellOwner(self, self._bus_name, objid)
self._buddies[self._owner.props.key] = self._owner
self._registry = ManagerRegistry()
@@ -92,8 +104,15 @@ class PresenceService(dbus.service.Object):
async_err_cb(exc)
def _server_status_cb(self, plugin, status, reason):
+ # FIXME: figure out connection status when we have a salut plugin too
+ old_status = self._connected
if status == CONNECTION_STATUS_CONNECTED:
- pass
+ self._connected = True
+ else:
+ self._connected = False
+
+ if self._connected != old_status:
+ self.emit('connection-status', self._connected)
def _contact_online(self, tp, handle, props):
new_buddy = False
@@ -120,8 +139,11 @@ class PresenceService(dbus.service.Object):
else:
self.BuddyDisappeared(buddy.object_path())
logging.debug("Buddy left: %s (%s)" % (buddy.props.nick, buddy.props.color))
-
+
def _contact_offline(self, tp, handle):
+ if not self._handles_buddies[tp].has_key(handle):
+ return
+
buddy = self._handles_buddies[tp].pop(handle)
key = buddy.props.key
@@ -144,11 +166,11 @@ class PresenceService(dbus.service.Object):
logging.debug("Buddy %s icon updated" % buddy.props.nick)
buddy.props.icon = avatar
- def _buddy_properties_changed(self, tp, handle, prop):
+ def _buddy_properties_changed(self, tp, handle, properties):
buddy = self._handles_buddies[tp].get(handle)
if buddy:
- buddy.set_properties(prop)
- logging.debug("Buddy %s properties updated" % buddy.props.nick)
+ buddy.set_properties(properties)
+ logging.debug("Buddy %s properties updated: %s" % (buddy.props.nick, properties.keys()))
def _new_activity(self, activity_id, tp):
try:
@@ -257,11 +279,10 @@ class PresenceService(dbus.service.Object):
@dbus.service.method(_PRESENCE_INTERFACE, in_signature="s", out_signature="o")
def GetActivityById(self, actid):
- if self._activities.has_key(actid):
- act = self._activities[actid]
- if act.props.valid:
- return act.object_path()
- raise NotFoundError("The activity was not found.")
+ act = self.internal_get_activity(actid)
+ if not act or not act.props.valid:
+ raise NotFoundError("The activity was not found.")
+ return act.object_path()
@dbus.service.method(_PRESENCE_INTERFACE, out_signature="ao")
def GetBuddies(self):
@@ -330,6 +351,11 @@ class PresenceService(dbus.service.Object):
if activity:
activity.set_properties(props)
+ def internal_get_activity(self, actid):
+ if not self._activities.has_key(actid):
+ return None
+ return self._activities[actid]
+
def main(test=False):
loop = gobject.MainLoop()
diff --git a/services/presence/psutils.py b/services/presence/psutils.py
index 995ea3a..76583d6 100644
--- a/services/presence/psutils.py
+++ b/services/presence/psutils.py
@@ -16,7 +16,11 @@
def bytes_to_string(bytes):
- # Handle both DBus byte arrays and strings
+ """The function converts a D-BUS byte array provided by dbus to string format.
+
+ bytes -- a D-Bus array of bytes. Handle both DBus byte arrays and strings
+
+ """
try:
# DBus Byte array
ret = ''.join([chr(item) for item in bytes])
diff --git a/services/presence/server_plugin.py b/services/presence/server_plugin.py
index 5180692..5330567 100644
--- a/services/presence/server_plugin.py
+++ b/services/presence/server_plugin.py
@@ -1,3 +1,4 @@
+"""Telepathy-python presence server interface/implementation plugin"""
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd.
#
@@ -41,7 +42,7 @@ CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
_PROTOCOL = "jabber"
class InvalidBuddyError(Exception):
- pass
+ """(Unused) exception to indicate an invalid buddy specifier"""
def _buddy_icon_save_cb(buf, data):
data[0] += buf
@@ -76,6 +77,13 @@ def _get_buddy_icon_at_size(icon, maxw, maxh, maxsize):
class ServerPlugin(gobject.GObject):
+ """Telepathy-python-based presence server interface
+
+ The ServerPlugin instance translates network events from
+ Telepathy Python into GObject events. It provides direct
+ python calls to perform the required network operations
+ to implement the PresenceService.
+ """
__gsignals__ = {
'contact-online': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
@@ -104,6 +112,14 @@ class ServerPlugin(gobject.GObject):
}
def __init__(self, registry, owner):
+ """Initialize the ServerPlugin instance
+
+ registry -- telepathy.client.ManagerRegistry from the
+ PresenceService, used to find the "gabble" connection
+ manager in this case...
+ owner -- presence.buddy.GenericOwner instance (normally a
+ presence.buddy.ShellOwner instance)
+ """
gobject.GObject.__init__(self)
self._icon_cache = BuddyIconCache()
@@ -125,6 +141,20 @@ class ServerPlugin(gobject.GObject):
self._reconnect_id = 0
def _owner_property_changed_cb(self, owner, properties):
+ """Local user's configuration properties have changed
+
+ owner -- the Buddy object for the local user
+ properties -- set of updated properties
+
+ calls:
+
+ _set_self_current_activity current-activity
+ _set_self_alias nick
+ _set_self_olpc_properties color
+
+ depending on which properties are present in the
+ set of properties.
+ """
logging.debug("Owner properties changed: %s" % properties)
if properties.has_key("current-activity"):
@@ -137,10 +167,21 @@ class ServerPlugin(gobject.GObject):
self._set_self_olpc_properties()
def _owner_icon_changed_cb(self, owner, icon):
+ """Owner has changed their icon, forward to network"""
logging.debug("Owner icon changed to size %d" % len(str(icon)))
self._set_self_avatar(icon)
def _get_account_info(self):
+ """Retrieve metadata dictionary describing this account
+
+ returns dictionary with:
+
+ server : server url from owner
+ account : printable-ssh-key-hash@server
+ password : ssh-key-hash
+ register : whether to register (i.e. whether not yet
+ registered)
+ """
account_info = {}
account_info['server'] = self._owner.get_server()
@@ -155,6 +196,16 @@ class ServerPlugin(gobject.GObject):
return account_info
def _find_existing_connection(self):
+ """Try to find an existing Telepathy connection to this server
+
+ filters the set of connections from
+ telepathy.client.Connection.get_connections
+ to find a connection using our protocol with the
+ "self handle" of that connection being a handle
+ which matches our account (see _get_account_info)
+
+ returns connection or None
+ """
our_name = self._account['account']
# Search existing connections, if any, that we might be able to use
@@ -173,9 +224,20 @@ class ServerPlugin(gobject.GObject):
return None
def get_connection(self):
+ """Retrieve our telepathy.client.Connection object"""
return self._conn
def _init_connection(self):
+ """Set up our connection
+
+ if there is no existing connection
+ (_find_existing_connection returns None)
+ produce a new connection with our protocol for our
+ account.
+
+ if there is an existing connection, reuse it by
+ registering for various of events on it.
+ """
conn = self._find_existing_connection()
if not conn:
acct = self._account.copy()
@@ -201,6 +263,10 @@ class ServerPlugin(gobject.GObject):
return conn
def _request_list_channel(self, name):
+ """Request a contact-list channel from Telepathy
+
+ name -- publish/subscribe, for the type of channel
+ """
handle = self._conn[CONN_INTERFACE].RequestHandles(
CONNECTION_HANDLE_TYPE_LIST, [name])[0]
chan_path = self._conn[CONN_INTERFACE].RequestChannel(
@@ -212,6 +278,9 @@ class ServerPlugin(gobject.GObject):
return channel
def _connected_cb(self):
+ """Callback on successful connection to a server
+ """
+
if self._account['register']:
# we successfully register this account
self._owner.props.registered = True
@@ -257,7 +326,7 @@ class ServerPlugin(gobject.GObject):
self._activity_properties_changed_cb)
# Set initial buddy properties, avatar, and activities
- self._set_self_olpc_properties(True)
+ self._set_self_olpc_properties()
self._set_self_alias()
self._set_self_activities()
self._set_self_current_activity()
@@ -324,34 +393,58 @@ class ServerPlugin(gobject.GObject):
def _internal_join_activity(self, activity_id, signal, userdata):
handle = self._activities.get(activity_id)
if not handle:
- self._conn[CONN_INTERFACE].RequestHandles(CONNECTION_HANDLE_TYPE_ROOM, [activity_id],
+ # FIXME: figure out why the server can't figure this out itself
+ room_jid = activity_id + "@conference." + self._account["server"]
+ self._conn[CONN_INTERFACE].RequestHandles(CONNECTION_HANDLE_TYPE_ROOM, [room_jid],
reply_handler=lambda *args: self._join_activity_get_channel_cb(activity_id, signal, userdata, *args),
error_handler=lambda *args: self._join_error_cb(activity_id, signal, userdata, *args))
else:
self._join_activity_get_channel_cb(activity_id, userdata, [handle])
def share_activity(self, activity_id, userdata):
+ """Share activity with the network
+
+ activity_id -- unique ID for the activity
+ userdata -- opaque token to be passed in the resulting event
+ (id, callback, errback) normally
+
+ Asks the Telepathy server to create a "conference" channel
+ for the activity or return a handle to an already created
+ conference channel for the activity.
+ """
self._internal_join_activity(activity_id, "activity-shared", userdata)
def join_activity(self, activity_id, userdata):
+ """Join an activity on the network (or locally)
+
+ activity_id -- unique ID for the activity
+ userdata -- opaque token to be passed in the resulting event
+ (id, callback, errback) normally
+
+ Asks the Telepathy server to create a "conference" channel
+ for the activity or return a handle to an already created
+ conference channel for the activity.
+ """
self._internal_join_activity(activity_id, "activity-joined", userdata)
def _ignore_success_cb(self):
- pass
+ """Ignore an event (null-operation)"""
def _log_error_cb(self, msg, err):
+ """Log a message (error) at debug level with prefix msg"""
logging.debug("Error %s: %s" % (msg, err))
- def _set_self_olpc_properties(self, set_key=False):
+ def _set_self_olpc_properties(self):
+ """Set color and key on our Telepathy server identity"""
props = {}
props['color'] = self._owner.props.color
- if set_key:
- props['key'] = dbus.ByteArray(self._owner.props.key)
+ props['key'] = dbus.ByteArray(self._owner.props.key)
self._conn[CONN_INTERFACE_BUDDY_INFO].SetProperties(props,
reply_handler=self._ignore_success_cb,
error_handler=lambda *args: self._log_error_cb("setting properties", *args))
def _set_self_alias(self):
+ """Forwarded to SetActivities on AliasInfo channel"""
alias = self._owner.props.nick
self_handle = self._conn[CONN_INTERFACE].GetSelfHandle()
self._conn[CONN_INTERFACE_ALIASING].SetAliases({self_handle : alias},
@@ -359,11 +452,19 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("setting alias", *args))
def _set_self_activities(self):
+ """Forward set of joined activities to network
+
+ uses SetActivities on BuddyInfo channel
+ """
self._conn[CONN_INTERFACE_BUDDY_INFO].SetActivities(self._joined_activities,
reply_handler=self._ignore_success_cb,
error_handler=lambda *args: self._log_error_cb("setting activities", *args))
def _set_self_current_activity(self):
+ """Forward our current activity (or "") to network
+
+ uses SetCurrentActivity on BuddyInfo channel
+ """
cur_activity = self._owner.props.current_activity
cur_activity_handle = 0
if not cur_activity:
@@ -381,12 +482,20 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("setting current activity", *args))
def _get_handle_for_activity(self, activity_id):
+ """Retrieve current handle for given activity or None"""
for (act, handle) in self._joined_activities:
if activity_id == act:
return handle
return None
def _status_changed_cb(self, state, reason):
+ """Handle notification of connection-status change
+
+ state -- CONNECTION_STATUS_*
+ reason -- integer code describing the reason...
+
+ returns False XXX what does that mean?
+ """
if state == CONNECTION_STATUS_CONNECTING:
logging.debug("State: connecting...")
elif state == CONNECTION_STATUS_CONNECTED:
@@ -403,6 +512,16 @@ class ServerPlugin(gobject.GObject):
return False
def start(self):
+ """Start up the Telepathy networking connections
+
+ if we are already connected, query for the initial contact
+ information.
+
+ if we are already connecting, do nothing
+
+ otherwise initiate a connection and transfer control to
+ _connect_reply_cb or _connect_error_cb
+ """
logging.debug("Starting up...")
# If the connection is already connected query initial contacts
conn_status = self._conn[CONN_INTERFACE].GetStatus()
@@ -418,25 +537,30 @@ class ServerPlugin(gobject.GObject):
error_handler=self._connect_error_cb)
def _connect_reply_cb(self):
+ """Handle connection success"""
if self._reconnect_id > 0:
gobject.source_remove(self._reconnect_id)
def _reconnect(self):
+ """Reset number-of-attempted connections and re-attempt"""
self._reconnect_id = 0
self.start()
return False
def _connect_error_cb(self, exception):
+ """Handle connection failure"""
logging.debug("Connect error: %s" % exception)
if not self._reconnect_id:
self._reconnect_id = gobject.timeout_add(10000, self._reconnect)
def cleanup(self):
+ """If we still have a connection, disconnect it"""
if not self._conn:
return
self._conn[CONN_INTERFACE].Disconnect()
def _contact_offline(self, handle):
+ """Handle contact going offline (send message, update set)"""
if not self._online_contacts.has_key(handle):
return
if self._online_contacts[handle]:
@@ -444,13 +568,16 @@ class ServerPlugin(gobject.GObject):
del self._online_contacts[handle]
def _contact_online_activities_cb(self, handle, activities):
+ """Handle contact's activity list update"""
self._buddy_activities_changed_cb(handle, activities)
def _contact_online_activities_error_cb(self, handle, err):
+ """Handle contact's activity list being unavailable"""
logging.debug("Handle %s - Error getting activities: %s" % (handle, err))
self._contact_offline(handle)
def _contact_online_aliases_cb(self, handle, props, aliases):
+ """Handle contact's alias being received (do further queries)"""
if not aliases or not len(aliases):
logging.debug("Handle %s - No aliases" % handle)
self._contact_offline(handle)
@@ -466,10 +593,12 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._contact_online_activities_error_cb(handle, *args))
def _contact_online_aliases_error_cb(self, handle, err):
+ """Handle failure to retrieve given user's alias/information"""
logging.debug("Handle %s - Error getting nickname: %s" % (handle, err))
self._contact_offline(handle)
def _contact_online_properties_cb(self, handle, props):
+ """Handle failure to retrieve given user's alias/information"""
if not props.has_key('key'):
logging.debug("Handle %s - invalid key." % handle)
self._contact_offline(handle)
@@ -487,16 +616,19 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._contact_online_aliases_error_cb(handle, *args))
def _contact_online_properties_error_cb(self, handle, err):
+ """Handle error retrieving property-set for a user (handle)"""
logging.debug("Handle %s - Error getting properties: %s" % (handle, err))
self._contact_offline(handle)
def _contact_online(self, handle):
+ """Handle a contact coming online"""
self._online_contacts[handle] = None
self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(handle,
reply_handler=lambda *args: self._contact_online_properties_cb(handle, *args),
error_handler=lambda *args: self._contact_online_properties_error_cb(handle, *args))
def _presence_update_cb(self, presence):
+ """Send update for online/offline status of presence"""
for handle in presence:
timestamp, statuses = presence[handle]
online = handle in self._online_contacts
@@ -511,18 +643,19 @@ class ServerPlugin(gobject.GObject):
self._contact_offline(handle)
def _avatar_updated_cb(self, handle, new_avatar_token):
+ """Handle update of given user (handle)'s avatar"""
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner property changes since those
# are handled locally
return
if not self._online_contacts.has_key(handle):
- logging.debug("Handle %s not valid yet...")
+ logging.debug("Handle %s unknown." % handle)
return
jid = self._online_contacts[handle]
if not jid:
- logging.debug("Handle %s not valid yet...")
+ logging.debug("Handle %s not valid yet..." % handle)
return
icon = self._icon_cache.get_icon(jid, new_avatar_token)
@@ -535,6 +668,7 @@ class ServerPlugin(gobject.GObject):
self.emit("avatar-updated", handle, icon)
def _alias_changed_cb(self, aliases):
+ """Handle update of aliases for all users"""
for handle, alias in aliases:
prop = {'nick': alias}
#print "Buddy %s alias changed to %s" % (handle, alias)
@@ -542,14 +676,16 @@ class ServerPlugin(gobject.GObject):
self._buddy_properties_changed_cb(handle, prop)
def _buddy_properties_changed_cb(self, handle, properties):
+ """Handle update of given user (handle)'s properties"""
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner property changes since those
# are handled locally
return
if self._online_contacts.has_key(handle) and self._online_contacts[handle]:
- self.emit("buddy-properties-changed", handle, properties)
+ self.emit("buddy-properties-changed", handle, properties)
def _buddy_activities_changed_cb(self, handle, activities):
+ """Handle update of given user (handle)'s activities"""
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner activity changes since those
# are handled locally
@@ -563,6 +699,8 @@ class ServerPlugin(gobject.GObject):
self.emit("buddy-activities-changed", handle, activities_id)
def _buddy_current_activity_changed_cb(self, handle, activity, channel):
+ """Handle update of given user (handle)'s current activity"""
+
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner current activity changes since those
# are handled locally
@@ -577,6 +715,8 @@ class ServerPlugin(gobject.GObject):
self._buddy_properties_changed_cb(handle, prop)
def _new_channel_cb(self, object_path, channel_type, handle_type, handle, suppress_handler):
+ """Handle creation of a new channel
+ """
if handle_type == CONNECTION_HANDLE_TYPE_ROOM and channel_type == CHANNEL_TYPE_TEXT:
channel = Channel(self._conn._dbus_object._named_service, object_path)
@@ -595,6 +735,7 @@ class ServerPlugin(gobject.GObject):
self.emit("private-invitation", object_path)
def update_activity_properties(self, act_id):
+ """Request update from network on the activity properties of act_id"""
handle = self._activities.get(act_id)
if not handle:
raise RuntimeError("Unknown activity %s: couldn't find handle.")
@@ -604,6 +745,7 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("getting activity properties", *args))
def set_activity_properties(self, act_id, props):
+ """Send update to network on the activity properties of act_id (props)"""
handle = self._activities.get(act_id)
if not handle:
raise RuntimeError("Unknown activity %s: couldn't find handle.")
@@ -613,6 +755,7 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("setting activity properties", *args))
def _activity_properties_changed_cb(self, room, properties):
+ """Handle update of properties for a "room" (activity handle)"""
for act_id, act_handle in self._activities.items():
if room == act_handle:
self.emit("activity-properties-changed", act_id, properties)
diff --git a/services/presence/sugar-presence-service b/services/presence/sugar-presence-service
index bba32b5..7eec696 100755
--- a/services/presence/sugar-presence-service
+++ b/services/presence/sugar-presence-service
@@ -24,7 +24,7 @@ import os
from sugar import logger
from sugar import env
-sys.path.insert(0, env.get_service_path('presence'))
+sys.path.append(env.get_service_path('presence'))
test=0
if len(sys.argv) > 1: