Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar
diff options
context:
space:
mode:
Diffstat (limited to 'sugar')
-rw-r--r--sugar/activity/activity.py3
-rw-r--r--sugar/presence/activity.py113
-rw-r--r--sugar/presence/buddy.py29
-rw-r--r--sugar/presence/presenceservice.py52
4 files changed, 133 insertions, 64 deletions
diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py
index 99070d2..85f660c 100644
--- a/sugar/activity/activity.py
+++ b/sugar/activity/activity.py
@@ -510,6 +510,9 @@ class Activity(Window, gtk.Container):
private -- bool: True to share by invitation only,
False to advertise as shared to everyone.
+
+ Once the activity is shared, its privacy can be changed by setting
+ its 'private' property.
"""
# FIXME: Make private=True to turn on the by-invitation-only scope
if self._shared_activity and self._shared_activity.props.joined:
diff --git a/sugar/presence/activity.py b/sugar/presence/activity.py
index bdfc74f..c4c0a47 100644
--- a/sugar/presence/activity.py
+++ b/sugar/presence/activity.py
@@ -16,9 +16,13 @@
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
+import logging
+
import gobject
import dbus
+_logger = logging.getLogger('sugar.presence.activity')
+
class Activity(gobject.GObject):
"""UI interface for an Activity in the presence service
@@ -41,15 +45,17 @@ class Activity(gobject.GObject):
'new-channel': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
}
__gproperties__ = {
'id' : (str, None, None, None, gobject.PARAM_READABLE),
- 'name' : (str, None, None, None, gobject.PARAM_READABLE),
- 'color' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'name' : (str, None, None, None, gobject.PARAM_READWRITE),
+ 'tags' : (str, None, None, None, gobject.PARAM_READWRITE),
+ 'color' : (str, None, None, None, gobject.PARAM_READWRITE),
'type' : (str, None, None, None, gobject.PARAM_READABLE),
- 'joined' : (bool, None, None, False, gobject.PARAM_READABLE)
+ 'private' : (bool, None, None, True, gobject.PARAM_READWRITE),
+ 'joined' : (bool, None, None, False, gobject.PARAM_READABLE),
}
_PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
@@ -66,37 +72,106 @@ class Activity(gobject.GObject):
self._activity.connect_to_signal('BuddyJoined', self._buddy_joined_cb)
self._activity.connect_to_signal('BuddyLeft', self._buddy_left_cb)
self._activity.connect_to_signal('NewChannel', self._new_channel_cb)
+ self._activity.connect_to_signal('PropertiesChanged',
+ self._properties_changed_cb,
+ utf8_strings=True)
+ # FIXME: this *would* just use a normal proxy call, but I want the
+ # pending call object so I can block on it, and normal proxy methods
+ # don't return those as of dbus-python 0.82.1; so do it the hard way
+ self._get_properties_call = bus.call_async(self._PRESENCE_SERVICE,
+ object_path, self._ACTIVITY_DBUS_INTERFACE, 'GetProperties',
+ '', (), self._get_properties_reply_cb,
+ self._get_properties_error_cb, utf8_strings=True)
self._id = None
self._color = None
self._name = None
self._type = None
+ self._tags = None
+ self._private = True
self._joined = False
+ def _get_properties_reply_cb(self, new_props):
+ self._properties_changed_cb(new_props)
+ self._get_properties_call = None
+
+ def _get_properties_error_cb(self, e):
+ self._get_properties_call = None
+ # FIXME: do something with the error
+ _logger.warning('Error doing initial GetProperties: %s', e)
+
+ def _properties_changed_cb(self, new_props):
+ _logger.debug('Activity properties changed to %r', new_props)
+ val = new_props.get('name', self._name)
+ if isinstance(val, str) and val != self._name:
+ self._name = val
+ self.notify('name')
+ val = new_props.get('tags', self._tags)
+ if isinstance(val, str) and val != self._tags:
+ self._tags = val
+ self.notify('tags')
+ val = new_props.get('color', self._color)
+ if isinstance(val, str) and val != self._color:
+ self._color = val
+ self.notify('color')
+ val = bool(new_props.get('private', self._private))
+ if val != self._private:
+ self._private = val
+ self.notify('private')
+ val = new_props.get('id', self._id)
+ if isinstance(val, str) and self._id is None:
+ self._id = val
+ self.notify('id')
+ val = new_props.get('type', self._type)
+ if isinstance(val, str) and self._type is None:
+ self._type = val
+ self.notify('type')
+
def object_path(self):
"""Get our dbus object path"""
return self._object_path
def do_get_property(self, pspec):
"""Retrieve a particular property from our property dictionary"""
+ _logger.debug('Looking up property %s', pspec.name)
+
+ if pspec.name == "joined":
+ return self._joined
+
+ if self._get_properties_call is not None:
+ _logger.debug('Blocking on GetProperties() because someone wants '
+ 'property %s', pspec.name)
+ self._get_properties_call.block()
+
if pspec.name == "id":
- if not self._id:
- self._id = self._activity.GetId()
return self._id
elif pspec.name == "name":
- if not self._name:
- self._name = self._activity.GetName()
return self._name
elif pspec.name == "color":
- if not self._color:
- self._color = self._activity.GetColor()
return self._color
elif pspec.name == "type":
- if not self._type:
- self._type = self._activity.GetType()
return self._type
- elif pspec.name == "joined":
- return self._joined
+ elif pspec.name == "tags":
+ return self._tags
+ elif pspec.name == "private":
+ return self._private
+
+ # FIXME: need an asynchronous API to set these properties, particularly
+ # 'private'
+ def do_set_property(self, pspec, val):
+ """Set a particular property in our property dictionary"""
+ if pspec.name == "name":
+ self._activity.SetProperties({'name': val})
+ self._name = val
+ elif pspec.name == "color":
+ self._activity.SetProperties({'color': val})
+ self._color = val
+ elif pspec.name == "tags":
+ self._activity.SetProperties({'tags': val})
+ self._tags = val
+ elif pspec.name == "private":
+ self._activity.SetProperties({'private': val})
+ self._private = val
def _emit_buddy_joined_signal(self, object_path):
"""Generate buddy-joined GObject signal with presence Buddy object"""
@@ -139,6 +214,16 @@ class Activity(gobject.GObject):
buddies.append(self._ps_new_object(item))
return buddies
+ def invite(self, buddy, message, response_cb):
+ """Invite the given buddy to join this activity.
+
+ The callback will be called with one parameter: None on success,
+ or an exception on failure.
+ """
+ self._activity.Invite(buddy.object_path(), message,
+ reply_handler=lambda: response_cb(None),
+ error_handler=response_cb)
+
def _join_cb(self):
self._joined = True
self.emit("joined", True, None)
diff --git a/sugar/presence/buddy.py b/sugar/presence/buddy.py
index 809748f..2748299 100644
--- a/sugar/presence/buddy.py
+++ b/sugar/presence/buddy.py
@@ -20,17 +20,6 @@ import gobject
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
@@ -61,7 +50,7 @@ class Buddy(gobject.GObject):
__gproperties__ = {
'key' : (str, None, None, None, gobject.PARAM_READABLE),
- 'icon' : (object, None, None, gobject.PARAM_READABLE),
+ 'icon' : (str, None, None, None, gobject.PARAM_READABLE),
'nick' : (str, None, None, None, gobject.PARAM_READABLE),
'color' : (str, None, None, None, gobject.PARAM_READABLE),
'current-activity' : (object, None, None, gobject.PARAM_READABLE),
@@ -89,7 +78,8 @@ class Buddy(gobject.GObject):
bobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
self._buddy = dbus.Interface(bobj, self._BUDDY_DBUS_INTERFACE)
- self._buddy.connect_to_signal('IconChanged', self._icon_changed_cb)
+ self._buddy.connect_to_signal('IconChanged', self._icon_changed_cb,
+ byte_arrays=True)
self._buddy.connect_to_signal('JoinedActivity', self._joined_activity_cb)
self._buddy.connect_to_signal('LeftActivity', self._left_activity_cb)
self._buddy.connect_to_signal('PropertyChanged', self._property_changed_cb)
@@ -134,7 +124,7 @@ class Buddy(gobject.GObject):
return self._properties["owner"]
elif pspec.name == "icon":
if not self._icon:
- self._icon = _bytes_to_string(self._buddy.GetIcon())
+ self._icon = str(self._buddy.GetIcon(byte_arrays=True))
return self._icon
elif pspec.name == "ip4-address":
# IPv4 address will go away quite soon
@@ -148,7 +138,7 @@ class Buddy(gobject.GObject):
def _emit_icon_changed_signal(self, bytes):
"""Emit GObject signal when icon has changed"""
- self._icon = _bytes_to_string(bytes)
+ self._icon = str(bytes)
self.emit('icon-changed')
return False
@@ -210,14 +200,7 @@ class Buddy(gobject.GObject):
"""
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.write(self.props.icon)
pbl.close()
return pbl.get_pixbuf()
else:
diff --git a/sugar/presence/presenceservice.py b/sugar/presence/presenceservice.py
index 7a9f76d..b46b2fa 100644
--- a/sugar/presence/presenceservice.py
+++ b/sugar/presence/presenceservice.py
@@ -47,8 +47,8 @@ class PresenceService(gobject.GObject):
([gobject.TYPE_PYOBJECT])),
'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
- 'activity-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT])),
+ # parameters: (activity: Activity, inviter: Buddy, message: unicode)
+ 'activity-invitation': (gobject.SIGNAL_RUN_FIRST, None, ([object]*3)),
'private-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
gobject.TYPE_PYOBJECT])),
@@ -205,14 +205,17 @@ class PresenceService(gobject.GObject):
"""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):
+ def _emit_activity_invitation_signal(self, activity_path, buddy_path,
+ message):
"""Emit GObject event with presence.activity.Activity object"""
- self.emit('activity-invitation', self._new_object(object_path))
+ self.emit('activity-invitation', self._new_object(activity_path),
+ self._new_object(buddy_path), unicode(message))
return False
- def _activity_invitation_cb(self, object_path):
+ def _activity_invitation_cb(self, activity_path, buddy_path, message):
"""Callback for dbus event (forwards to method to emit GObject event)"""
- gobject.idle_add(self._emit_activity_invitation_signal, object_path)
+ gobject.idle_add(self._emit_activity_invitation_signal, activity_path,
+ buddy_path, message)
def _emit_private_invitation_signal(self, bus_name, connection, channel):
"""Emit GObject event with bus_name, connection and channel"""
@@ -420,16 +423,22 @@ class PresenceService(gobject.GObject):
raise RuntimeError("Could not get owner object from presence service.")
return self._new_object(owner_op)
- def _share_activity_cb(self, activity, op):
+ def _share_activity_cb(self, activity, psact):
"""Notify with GObject event of successful sharing of activity
-
- op -- full dbus path of the new object, must be
- prefixed with either of _PS_BUDDY_OP or _PS_ACTIVITY_OP
"""
- psact = self._new_object(op)
psact._joined = True
self.emit("activity-shared", True, psact, None)
+ def _share_activity_privacy_cb(self, activity, private, op):
+ psact = self._new_object(op)
+ # FIXME: this should be done asynchronously (more API needed)
+ try:
+ psact.props.private = private
+ except Exception, e:
+ self._share_activity_error_cb(activity, e)
+ else:
+ self._share_activity_cb(activity, psact)
+
def _share_activity_error_cb(self, activity, err):
"""Notify with GObject event of unsuccessful sharing of activity"""
_logger.debug("Error sharing activity %s: %s" % (activity.get_id(), err))
@@ -449,7 +458,6 @@ class PresenceService(gobject.GObject):
returns None
"""
actid = activity.get_id()
- _logger.debug('XXXX in share_activity')
# Ensure the activity is not already shared/joined
for obj in self._objcache.values():
@@ -461,21 +469,11 @@ class PresenceService(gobject.GObject):
atype = activity.get_service_name()
name = activity.props.title
- if private:
- _logger.debug('XXXX private, so calling InviteActivity')
- self._ps.InviteActivity(actid, atype, name, properties,
- reply_handler=lambda *args: \
- self._share_activity_cb(activity, *args),
- error_handler=lambda *args: \
- self._share_activity_error_cb(activity, *args))
- else:
- # FIXME: Test, then make this AdvertiseActivity:
- _logger.debug('XXXX not private, so calling ShareActivity')
- self._ps.ShareActivity(actid, atype, name, properties,
- reply_handler=lambda *args: \
- self._share_activity_cb(activity, *args),
- error_handler=lambda *args: \
- self._share_activity_error_cb(activity, *args))
+ self._ps.ShareActivity(actid, atype, name, properties,
+ reply_handler=lambda op: \
+ self._share_activity_privacy_cb(activity, private, op),
+ error_handler=lambda e: \
+ self._share_activity_error_cb(activity, e))
def get_preferred_connection(self):
"""Gets the preferred telepathy connection object that an activity