Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar
diff options
context:
space:
mode:
authorMarco Pesenti Gritti <marco@localhost.localdomain>2007-04-15 09:42:06 (GMT)
committer Marco Pesenti Gritti <marco@localhost.localdomain>2007-04-15 09:42:06 (GMT)
commitee400fa602cccbac95e2d1f4c36b21d36ff994d7 (patch)
tree5410431d6ad892c5d06e6acb119a84f3c25240d5 /sugar
parentc738ac488e457d877d6da05f50e953524fb4cfdb (diff)
parent8635a702c64f087473a1c2243bcc895bcbd8b0c4 (diff)
Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar
Diffstat (limited to 'sugar')
-rw-r--r--sugar/clipboard/__init__.py5
-rw-r--r--sugar/graphics/__init__.py1
-rw-r--r--sugar/presence/__init__.py7
-rw-r--r--sugar/presence/activity.py45
-rw-r--r--sugar/presence/buddy.py76
-rw-r--r--sugar/presence/presenceservice.py147
6 files changed, 278 insertions, 3 deletions
diff --git a/sugar/clipboard/__init__.py b/sugar/clipboard/__init__.py
index e69de29..c42ffce 100644
--- a/sugar/clipboard/__init__.py
+++ b/sugar/clipboard/__init__.py
@@ -0,0 +1,5 @@
+"""Client-code's interface to the ClipboardService
+
+Provides a simplified API for accessing the dbus service
+which coordinates clipboard operations within Sugar.
+"""
diff --git a/sugar/graphics/__init__.py b/sugar/graphics/__init__.py
index e69de29..0a148b4 100644
--- a/sugar/graphics/__init__.py
+++ b/sugar/graphics/__init__.py
@@ -0,0 +1 @@
+"""Hippo-based graphics/controls for use in Sugar"""
diff --git a/sugar/presence/__init__.py b/sugar/presence/__init__.py
index e69de29..f09a5e3 100644
--- a/sugar/presence/__init__.py
+++ b/sugar/presence/__init__.py
@@ -0,0 +1,7 @@
+"""Client-code's interface to the PresenceService
+
+Provides a simplified API for accessing the dbus service
+which coordinates native network presence and sharing
+information. This includes both "buddies" and "shared
+activities".
+"""
diff --git a/sugar/presence/activity.py b/sugar/presence/activity.py
index 1616378..f290368 100644
--- a/sugar/presence/activity.py
+++ b/sugar/presence/activity.py
@@ -1,3 +1,4 @@
+"""UI interface to an activity in the presence service"""
# Copyright (C) 2007, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
@@ -19,7 +20,19 @@ import gobject
import dbus
class Activity(gobject.GObject):
-
+ """UI interface for an Activity in the presence service
+
+ Activities in the presence service represent other user's
+ shared activities and your own activities (XXX shared or
+ otherwise?)
+
+ Properties:
+ id
+ color
+ name
+ type
+ joined
+ """
__gsignals__ = {
'buddy-joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
@@ -33,6 +46,7 @@ class Activity(gobject.GObject):
_ACTIVITY_DBUS_INTERFACE = "org.laptop.Sugar.Presence.Activity"
def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
+ """Initialse the activity interface, connecting to service"""
gobject.GObject.__init__(self)
self._object_path = object_path
self._ps_new_object = new_obj_cb
@@ -50,9 +64,11 @@ class Activity(gobject.GObject):
self._joined = False
def object_path(self):
+ """Get our dbus object path"""
return self._object_path
def _emit_buddy_joined_signal(self, object_path):
+ """Generate buddy-joined GObject signal with presence Buddy object"""
self.emit('buddy-joined', self._ps_new_object(object_path))
return False
@@ -60,6 +76,10 @@ class Activity(gobject.GObject):
gobject.idle_add(self._emit_buddy_joined_signal, object_path)
def _emit_buddy_left_signal(self, object_path):
+ """Generate buddy-left GObject signal with presence Buddy object
+
+ XXX note use of _ps_new_object instead of _ps_del_object here
+ """
self.emit('buddy-left', self._ps_new_object(object_path))
return False
@@ -67,6 +87,10 @@ class Activity(gobject.GObject):
gobject.idle_add(self._emit_buddy_left_signal, object_path)
def _emit_new_channel_signal(self, object_path):
+ """Generate new-channel GObject signal with channel object path
+
+ New telepathy-python communications channel has been opened
+ """
self.emit('new-channel', object_path)
return False
@@ -74,27 +98,35 @@ class Activity(gobject.GObject):
gobject.idle_add(self._emit_new_channel_signal, object_path)
def get_id(self):
+ """Retrieve the unique identifier for this activity instance"""
# Cache activity ID, which should never change anyway
if not self._id:
self._id = self._activity.GetId()
return self._id
def get_color(self):
+ """Retrieve the activity icon colour for this activity instance"""
if not self._color:
self._color = self._activity.GetColor()
return self._color
def get_name(self):
+ """Retrieve the activity name for this activity instance"""
if not self._name:
self._name = self._activity.GetName()
return self._name
def get_type(self):
+ """Retrieve the activity/bundle type for this activity instance"""
if not self._type:
self._type = self._activity.GetType()
return self._type
def get_joined_buddies(self):
+ """Retrieve the set of Buddy objects attached to this activity
+
+ returns list of presence Buddy objects
+ """
resp = self._activity.GetJoinedBuddies()
buddies = []
for item in resp:
@@ -102,15 +134,26 @@ class Activity(gobject.GObject):
return buddies
def join(self):
+ """Join this activity
+
+ XXX if these are all activities, can I join my own activity?
+ """
if self._joined:
return
self._activity.Join()
self._joined = True
def get_channels(self):
+ """Retrieve communications channel descriptions for the activity
+
+ Returns (bus name, connection, channels) for the activity
+
+ XXX what are those values?
+ """
(bus_name, connection, channels) = self._activity.GetChannels()
return bus_name, connection, channels
def owner_has_joined(self):
+ """Retrieve whether the owner of the activity is active within it"""
# FIXME
return False
diff --git a/sugar/presence/buddy.py b/sugar/presence/buddy.py
index c6f51d5..048f3ea 100644
--- a/sugar/presence/buddy.py
+++ b/sugar/presence/buddy.py
@@ -1,3 +1,4 @@
+"""UI interface to a buddy in the presence service"""
# Copyright (C) 2007, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
@@ -20,13 +21,33 @@ import gtk
import dbus
def _bytes_to_string(bytes):
+ """Convertes an short-int (char) array to a string
+
+ returns string or None for a null sequence
+ """
if len(bytes):
+ # if there's an internal buffer, we could use
+ # ctypes to pull it out without this...
return ''.join([chr(item) for item in bytes])
return None
class Buddy(gobject.GObject):
-
+ """UI interface for a Buddy in the presence service
+
+ Each buddy interface tracks a set of activities and properties
+ that can be queried to provide UI controls for manipulating
+ the presence interface.
+
+ Properties Dictionary:
+ 'key': public key,
+ 'nick': nickname ,
+ 'color': color (XXX what format),
+ 'current-activity': (XXX dbus path?),
+ 'owner': (XXX dbus path?),
+ 'icon': (XXX pixel data for an icon?)
+ See __gproperties__
+ """
__gsignals__ = {
'icon-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([])),
@@ -51,6 +72,13 @@ class Buddy(gobject.GObject):
_BUDDY_DBUS_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
+ """Initialise the reference to the buddy
+
+ bus -- dbus bus object
+ new_obj_cb -- callback to call when this buddy joins an activity
+ del_obj_cb -- callback to call when this buddy leaves an activity
+ object_path -- path to the buddy object
+ """
gobject.GObject.__init__(self)
self._object_path = object_path
self._ps_new_object = new_obj_cb
@@ -68,12 +96,19 @@ class Buddy(gobject.GObject):
self._icon = None
def _get_properties_helper(self):
+ """Retrieve the Buddy's property dictionary from the service object
+ """
props = self._buddy.GetProperties()
if not props:
return {}
return props
def do_get_property(self, pspec):
+ """Retrieve a particular property from our property dictionary
+
+ pspec -- XXX some sort of GTK specifier object with attributes
+ including 'name', 'active' and 'icon-name'
+ """
if pspec.name == "key":
return self._properties["key"]
elif pspec.name == "nick":
@@ -97,48 +132,79 @@ class Buddy(gobject.GObject):
return self._icon
def object_path(self):
+ """Retrieve our dbus object path"""
return self._object_path
def _emit_icon_changed_signal(self, bytes):
+ """Emit GObject signal when icon has changed"""
self._icon = _bytes_to_string(bytes)
self.emit('icon-changed')
return False
def _icon_changed_cb(self, icon_data):
+ """Handle dbus signal by emitting a GObject signal"""
gobject.idle_add(self._emit_icon_changed_signal, icon_data)
def _emit_joined_activity_signal(self, object_path):
+ """Emit activity joined signal with Activity object"""
self.emit('joined-activity', self._ps_new_object(object_path))
return False
def _joined_activity_cb(self, object_path):
+ """Handle dbus signal by emitting a GObject signal
+
+ Stores the activity in activities dictionary as well
+ """
if not self._activities.has_key(object_path):
self._activities[object_path] = self._ps_new_object(object_path)
gobject.idle_add(self._emit_joined_activity_signal, object_path)
def _emit_left_activity_signal(self, object_path):
+ """Emit activity left signal with Activity object
+
+ XXX this calls self._ps_new_object instead of self._ps_del_object,
+ which would seem to be the incorrect callback?
+ """
self.emit('left-activity', self._ps_new_object(object_path))
return False
def _left_activity_cb(self, object_path):
+ """Handle dbus signal by emitting a GObject signal
+
+ Also removes from the activities dictionary
+ """
if self._activities.has_key(object_path):
del self._activities[object_path]
gobject.idle_add(self._emit_left_activity_signal, object_path)
def _handle_property_changed_signal(self, prop_list):
+ """Emit property-changed signal with property dictionary
+
+ Generates a property-changed signal with the results of
+ _get_properties_helper()
+ """
self._properties = self._get_properties_helper()
# FIXME: don't leak unexposed property names
self.emit('property-changed', prop_list)
return False
def _property_changed_cb(self, prop_list):
+ """Handle dbus signal by emitting a GObject signal"""
gobject.idle_add(self._handle_property_changed_signal, prop_list)
def get_icon_pixbuf(self):
+ """Retrieve Buddy's icon as a GTK pixel buffer
+
+ XXX Why aren't the icons coming in as SVG?
+ """
if self.props.icon and len(self.props.icon):
pbl = gtk.gdk.PixbufLoader()
icon_data = ""
for item in self.props.icon:
+ # XXX this is a slow way to convert the data
+ # under Python 2.5 and below, collect in a
+ # list and then join with "", see
+ # _bytes_to_string in this module
icon_data = icon_data + chr(item)
pbl.write(icon_data)
pbl.close()
@@ -147,6 +213,14 @@ class Buddy(gobject.GObject):
return None
def get_joined_activities(self):
+ """Retrieve the set of all activities which this buddy has joined
+
+ Uses the GetJoinedActivities method on the service
+ object to produce object paths, wraps each in an
+ Activity object.
+
+ returns list of presence Activity objects
+ """
try:
resp = self._buddy.GetJoinedActivities()
except dbus.exceptions.DBusException:
diff --git a/sugar/presence/presenceservice.py b/sugar/presence/presenceservice.py
index 9b7bd3c..78f51e6 100644
--- a/sugar/presence/presenceservice.py
+++ b/sugar/presence/presenceservice.py
@@ -1,3 +1,4 @@
+"""UI class to access system-level presence object"""
# Copyright (C) 2007, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
@@ -18,24 +19,78 @@
import dbus, dbus.glib, gobject
import logging
+# XXX use absolute imports
+# from sugar.presence import buddy, activity
+# this *kind* of relative import is deprecated
+# with an explicit relative import slated to be
+# introduced (available in Python 2.5 with a __future__
+# import), that would read as:
+# from . import buddy, activity
+# see PEP: http://docs.python.org/whatsnew/pep-328.html
import buddy, activity
class ObjectCache(object):
+ """Path to Activity/Buddy object cache
+
+ On notification of a new object of either type the
+ PresenceService client stores the object's representation
+ in this object.
+
+ XXX Why not just sub-class dict? We're only adding two
+ methods then and we would have all of the other
+ standard operations on dictionaries.
+ """
def __init__(self):
+ """Initialise the cache"""
self._cache = {}
def get(self, object_path):
+ """Retrieve specified object from the cache
+
+ object_path -- full dbus path to the object
+
+ returns a presence.buddy.Buddy or presence.activity.Activity
+ instance or None if the object_path is not yet cached.
+
+ XXX could be written as return self._cache.get( object_path )
+ """
try:
return self._cache[object_path]
except KeyError:
return None
def add(self, obj):
+ """Adds given presence object to the cache
+
+ obj -- presence Buddy or Activity representation, the object's
+ object_path() method is used as the key for storage
+
+ returns None
+
+ XXX should raise an error on collisions, shouldn't it? or
+ return True/False to say whether the item was actually
+ added
+ """
op = obj.object_path()
if not self._cache.has_key(op):
self._cache[op] = obj
def remove(self, object_path):
+ """Remove the given presence object from the cache
+
+ object_path -- full dbus path to the object
+
+ returns None
+
+ XXX does two checks instead of one with a try:except for the
+ keyerror, normal case of deleting existing penalised as
+ a result.
+
+ try:
+ return self._cache.pop( key )
+ except KeyError:
+ return None
+ """
if self._cache.has_key(object_path):
del self._cache[object_path]
@@ -46,7 +101,13 @@ DBUS_PATH = "/org/laptop/Sugar/Presence"
class PresenceService(gobject.GObject):
-
+ """UI-side interface to the dbus presence service
+
+ This class provides UI programmers with simplified access
+ to the dbus service of the same name. It allows for observing
+ various events from the presence service as GObject events,
+ as well as some basic introspection queries.
+ """
__gsignals__ = {
'buddy-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
@@ -71,6 +132,7 @@ class PresenceService(gobject.GObject):
def __init__(self):
+ """Initialise the service and connect to events"""
gobject.GObject.__init__(self)
self._objcache = ObjectCache()
self._bus = dbus.SessionBus()
@@ -84,6 +146,17 @@ class PresenceService(gobject.GObject):
self._ps.connect_to_signal('PrivateInvitation', self._private_invitation_cb)
def _new_object(self, object_path):
+ """Turn new object path into (cached) Buddy/Activity instance
+
+ object_path -- full dbus path of the new object, must be
+ prefixed with either of _PS_BUDDY_OP or _PS_ACTIVITY_OP
+
+ Note that this method is called throughout the class whenever
+ the representation of the object is required, it is not only
+ called when the object is first discovered.
+
+ returns presence Buddy or Activity representation
+ """
obj = self._objcache.get(object_path)
if not obj:
if object_path.startswith(self._PS_BUDDY_OP):
@@ -102,52 +175,79 @@ class PresenceService(gobject.GObject):
pass
def _emit_buddy_appeared_signal(self, object_path):
+ """Emit GObject event with presence.buddy.Buddy object"""
self.emit('buddy-appeared', self._new_object(object_path))
return False
def _buddy_appeared_cb(self, op):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_buddy_appeared_signal, op)
def _emit_buddy_disappeared_signal(self, object_path):
+ """Emit GObject event with presence.buddy.Buddy object"""
self.emit('buddy-disappeared', self._new_object(object_path))
return False
def _buddy_disappeared_cb(self, object_path):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_buddy_disappeared_signal, object_path)
def _emit_activity_invitation_signal(self, object_path):
+ """Emit GObject event with presence.activity.Activity object"""
self.emit('activity-invitation', self._new_object(object_path))
return False
def _activity_invitation_cb(self, object_path):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_activity_invitation_signal, object_path)
def _emit_private_invitation_signal(self, bus_name, connection, channel):
+ """Emit GObject event with bus_name, connection and channel
+
+ XXX This seems to generate the wrong GObject event? It generates
+ 'service-disappeared' instead of private-invitation for some
+ reason. That event doesn't even seem to be registered?
+ """
self.emit('service-disappeared', bus_name, connection, channel)
return False
def _private_invitation_cb(self, bus_name, connection, channel):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_service_disappeared_signal, bus_name,
connection, channel)
def _emit_activity_appeared_signal(self, object_path):
+ """Emit GObject event with presence.activity.Activity object"""
self.emit('activity-appeared', self._new_object(object_path))
return False
def _activity_appeared_cb(self, object_path):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_activity_appeared_signal, object_path)
def _emit_activity_disappeared_signal(self, object_path):
+ """Emit GObject event with presence.activity.Activity object"""
self.emit('activity-disappeared', self._new_object(object_path))
return False
def _activity_disappeared_cb(self, object_path):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_activity_disappeared_signal, object_path)
def get(self, object_path):
+ """Retrieve given object path as a Buddy/Activity object
+
+ XXX This is basically just an alias for _new_object, i.e. it
+ just adds an extra function-call to the operation.
+ """
return self._new_object(object_path)
def get_activities(self):
+ """Retrieve set of all activities from service
+
+ returns list of Activity objects for all object paths
+ the service reports exist (using GetActivities)
+ """
resp = self._ps.GetActivities()
acts = []
for item in resp:
@@ -155,6 +255,13 @@ class PresenceService(gobject.GObject):
return acts
def get_activity(self, activity_id):
+ """Retrieve single Activity object for the given unique id
+
+ activity_id -- unique ID for the activity
+
+ returns single Activity object or None if the activity
+ is not found using GetActivityById on the service
+ """
try:
act_op = self._ps.GetActivityById(activity_id)
except dbus.exceptions.DBusException:
@@ -162,6 +269,11 @@ class PresenceService(gobject.GObject):
return self._new_object(act_op)
def get_buddies(self):
+ """Retrieve set of all buddies from service
+
+ returns list of Buddy objects for all object paths
+ the service reports exist (using GetBuddies)
+ """
resp = self._ps.GetBuddies()
buddies = []
for item in resp:
@@ -169,6 +281,14 @@ class PresenceService(gobject.GObject):
return buddies
def get_buddy(self, key):
+ """Retrieve single Buddy object for the given public key
+
+ key -- buddy's public encryption key
+
+ returns single Buddy object or None if the activity
+ is not found using GetBuddyByPublicKey on the
+ service
+ """
try:
buddy_op = self._ps.GetBuddyByPublicKey(dbus.ByteArray(key))
except dbus.exceptions.DBusException:
@@ -176,6 +296,12 @@ class PresenceService(gobject.GObject):
return self._new_object(buddy_op)
def get_owner(self):
+ """Retrieves "owner" as a Buddy
+
+ XXX check that it really is a Buddy that's produced, what is
+ this the owner of? Shouldn't it be getting an activity
+ and then asking who the owner of that is?
+ """
try:
owner_op = self._ps.GetOwner()
except dbus.exceptions.DBusException:
@@ -183,13 +309,27 @@ class PresenceService(gobject.GObject):
return self._new_object(owner_op)
def _share_activity_cb(self, activity, op):
+ """Notify with GObject event of successful sharing of activity"""
self.emit("activity-shared", True, self._new_object(op), None)
def _share_activity_error_cb(self, activity, err):
+ """Notify with GObject event of unsuccessful sharing of activity"""
logging.debug("Error sharing activity %s: %s" % (activity.get_id(), err))
self.emit("activity-shared", False, None, err)
def share_activity(self, activity, properties={}):
+ """Ask presence service to ask the activity to share itself
+
+ Uses the ShareActivity method on the service to ask for the
+ sharing of the given activity. Arranges to emit activity-shared
+ event with:
+
+ (success, Activity, err)
+
+ on success/failure.
+
+ returns None
+ """
actid = activity.get_id()
atype = activity.get_service_name()
name = activity.props.title
@@ -199,6 +339,10 @@ class PresenceService(gobject.GObject):
class _MockPresenceService(gobject.GObject):
+ """Test fixture allowing testing of items that use PresenceService
+
+ See PresenceService for usage and purpose
+ """
__gsignals__ = {
'buddy-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
@@ -238,6 +382,7 @@ class _MockPresenceService(gobject.GObject):
_ps = None
def get_instance():
+ """Retrieve this process' view of the PresenceService"""
global _ps
if not _ps:
_ps = PresenceService()