Cleanup the source structure
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
sugardir = $(pythondir)/sugar/presence
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
activity.py \
|
||||
buddy.py \
|
||||
tubeconn.py \
|
||||
presenceservice.py
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""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".
|
||||
"""
|
||||
|
||||
# Copyright (C) 2006-2007, Red Hat, Inc.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
# Boston, MA 02111-1307, USA.
|
||||
@@ -0,0 +1,290 @@
|
||||
"""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
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# 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
|
||||
|
||||
Activities in the presence service represent your and other user's
|
||||
shared activities.
|
||||
|
||||
Properties:
|
||||
id
|
||||
color
|
||||
name
|
||||
type
|
||||
joined
|
||||
"""
|
||||
__gsignals__ = {
|
||||
'buddy-joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'buddy-left': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'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])),
|
||||
}
|
||||
|
||||
__gproperties__ = {
|
||||
'id' : (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),
|
||||
'private' : (bool, None, None, True, gobject.PARAM_READWRITE),
|
||||
'joined' : (bool, None, None, False, gobject.PARAM_READABLE),
|
||||
}
|
||||
|
||||
_PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
|
||||
_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
|
||||
self._ps_del_object = del_obj_cb
|
||||
bobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
|
||||
self._activity = dbus.Interface(bobj, self._ACTIVITY_DBUS_INTERFACE)
|
||||
self._activity.connect_to_signal('BuddyHandleJoined',
|
||||
self._buddy_handle_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
|
||||
# Cache for get_buddy_by_handle, maps handles to buddy object paths
|
||||
self._handle_to_buddy_path = {}
|
||||
self._buddy_path_to_handle = {}
|
||||
|
||||
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":
|
||||
return self._id
|
||||
elif pspec.name == "name":
|
||||
return self._name
|
||||
elif pspec.name == "color":
|
||||
return self._color
|
||||
elif pspec.name == "type":
|
||||
return self._type
|
||||
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"""
|
||||
self.emit('buddy-joined', self._ps_new_object(object_path))
|
||||
return False
|
||||
|
||||
def _buddy_handle_joined_cb(self, object_path, handle):
|
||||
gobject.idle_add(self._emit_buddy_joined_signal, object_path)
|
||||
self._handle_to_buddy_path[handle] = object_path
|
||||
self._buddy_path_to_handle[object_path] = handle
|
||||
|
||||
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
|
||||
|
||||
def _buddy_left_cb(self, object_path):
|
||||
gobject.idle_add(self._emit_buddy_left_signal, object_path)
|
||||
handle = self._buddy_path_to_handle.pop(object_path)
|
||||
self._handle_to_buddy_path.pop(handle, None)
|
||||
|
||||
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
|
||||
|
||||
def _new_channel_cb(self, object_path):
|
||||
gobject.idle_add(self._emit_new_channel_signal, object_path)
|
||||
|
||||
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:
|
||||
buddies.append(self._ps_new_object(item))
|
||||
return buddies
|
||||
|
||||
def get_buddy_by_handle(self, handle):
|
||||
"""Retrieve the Buddy object given a telepathy handle.
|
||||
|
||||
buddy object paths are cached in self._handle_to_buddy_path,
|
||||
so we can get the buddy without calling PS.
|
||||
"""
|
||||
object_path = self._handle_to_buddy_path.get(handle, None)
|
||||
if object_path:
|
||||
buddy = self._ps_new_object(object_path)
|
||||
return buddy
|
||||
return None
|
||||
|
||||
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)
|
||||
|
||||
def _join_error_cb(self, err):
|
||||
self.emit("joined", False, str(err))
|
||||
|
||||
def join(self):
|
||||
"""Join this activity
|
||||
|
||||
XXX if these are all activities, can I join my own activity?
|
||||
"""
|
||||
if self._joined:
|
||||
self.emit("joined", True, None)
|
||||
return
|
||||
self._activity.Join(reply_handler=self._join_cb, error_handler=self._join_error_cb)
|
||||
|
||||
def get_channels(self):
|
||||
"""Retrieve communications channel descriptions for the activity
|
||||
|
||||
Returns a tuple containing:
|
||||
- the D-Bus well-known service name of the connection
|
||||
(FIXME: this is redundant; in Telepathy it can be derived
|
||||
from that of the connection)
|
||||
- the D-Bus object path of the connection
|
||||
- a list of D-Bus object paths representing the channels
|
||||
associated with this activity
|
||||
"""
|
||||
(bus_name, connection, channels) = self._activity.GetChannels()
|
||||
return bus_name, connection, channels
|
||||
|
||||
def _leave_cb(self):
|
||||
"""Callback for async action of leaving shared activity."""
|
||||
self.emit("joined", False, "left activity")
|
||||
|
||||
def _leave_error_cb(self, err):
|
||||
"""Callback for error in async leaving of shared activity."""
|
||||
_logger.debug('Failed to leave activity: %s', err)
|
||||
|
||||
def leave(self):
|
||||
"""Leave this shared activity"""
|
||||
self._joined = False
|
||||
self._activity.Leave(reply_handler=self._leave_cb,
|
||||
error_handler=self._leave_error_cb)
|
||||
@@ -0,0 +1,225 @@
|
||||
"""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
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
# Boston, MA 02111-1307, USA.
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
import dbus
|
||||
|
||||
|
||||
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,
|
||||
([])),
|
||||
'joined-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'left-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
}
|
||||
|
||||
__gproperties__ = {
|
||||
'key' : (str, None, 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),
|
||||
'owner' : (bool, None, None, False, gobject.PARAM_READABLE),
|
||||
'ip4-address' : (str, None, None, None, gobject.PARAM_READABLE)
|
||||
}
|
||||
|
||||
_PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
|
||||
_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
|
||||
self._ps_del_object = del_obj_cb
|
||||
self._properties = {}
|
||||
self._activities = {}
|
||||
|
||||
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,
|
||||
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)
|
||||
self._properties = self._get_properties_helper()
|
||||
|
||||
activities = self._buddy.GetJoinedActivities()
|
||||
for op in activities:
|
||||
self._activities[op] = self._ps_new_object(op)
|
||||
self._icon = None
|
||||
|
||||
def _get_properties_helper(self):
|
||||
"""Retrieve the Buddy's property dictionary from the service object
|
||||
"""
|
||||
props = self._buddy.GetProperties(byte_arrays=True)
|
||||
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":
|
||||
return self._properties["nick"]
|
||||
elif pspec.name == "color":
|
||||
return self._properties["color"]
|
||||
elif pspec.name == "current-activity":
|
||||
if not self._properties.has_key("current-activity"):
|
||||
return None
|
||||
curact = self._properties["current-activity"]
|
||||
if not len(curact):
|
||||
return None
|
||||
for activity in self._activities.values():
|
||||
if activity.props.id == curact:
|
||||
return activity
|
||||
return None
|
||||
elif pspec.name == "owner":
|
||||
return self._properties["owner"]
|
||||
elif pspec.name == "icon":
|
||||
if not self._icon:
|
||||
self._icon = str(self._buddy.GetIcon(byte_arrays=True))
|
||||
return self._icon
|
||||
elif pspec.name == "ip4-address":
|
||||
# IPv4 address will go away quite soon
|
||||
if not self._properties.has_key("ip4-address"):
|
||||
return None
|
||||
return self._properties["ip4-address"]
|
||||
|
||||
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 = str(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()
|
||||
pbl.write(self.props.icon)
|
||||
pbl.close()
|
||||
return pbl.get_pixbuf()
|
||||
else:
|
||||
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:
|
||||
return []
|
||||
acts = []
|
||||
for item in resp:
|
||||
acts.append(self._ps_new_object(item))
|
||||
return acts
|
||||
@@ -0,0 +1,570 @@
|
||||
"""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
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
# Boston, MA 02111-1307, USA.
|
||||
|
||||
import logging
|
||||
|
||||
import dbus
|
||||
import dbus.exceptions
|
||||
import dbus.glib
|
||||
import gobject
|
||||
|
||||
from sugar.presence.buddy import Buddy
|
||||
from sugar.presence.activity import Activity
|
||||
|
||||
|
||||
DBUS_SERVICE = "org.laptop.Sugar.Presence"
|
||||
DBUS_INTERFACE = "org.laptop.Sugar.Presence"
|
||||
DBUS_PATH = "/org/laptop/Sugar/Presence"
|
||||
|
||||
_logger = logging.getLogger('sugar.presence.presenceservice')
|
||||
|
||||
|
||||
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])),
|
||||
'buddy-disappeared': (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])),
|
||||
'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'activity-shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
|
||||
gobject.TYPE_PYOBJECT]))
|
||||
}
|
||||
|
||||
_PS_BUDDY_OP = DBUS_PATH + "/Buddies/"
|
||||
_PS_ACTIVITY_OP = DBUS_PATH + "/Activities/"
|
||||
|
||||
|
||||
def __init__(self, allow_offline_iface=True):
|
||||
"""Initialise the service and attempt to connect to events
|
||||
"""
|
||||
gobject.GObject.__init__(self)
|
||||
self._objcache = {}
|
||||
|
||||
# Get a connection to the session bus
|
||||
self._bus = dbus.SessionBus()
|
||||
self._bus.add_signal_receiver(self._name_owner_changed_cb,
|
||||
signal_name="NameOwnerChanged",
|
||||
dbus_interface="org.freedesktop.DBus")
|
||||
|
||||
# attempt to load the interface to the service...
|
||||
self._allow_offline_iface = allow_offline_iface
|
||||
self._get_ps()
|
||||
|
||||
def _name_owner_changed_cb(self, name, old, new):
|
||||
if name != DBUS_SERVICE:
|
||||
return
|
||||
if (old and len(old)) and (not new and not len(new)):
|
||||
# PS went away, clear out PS dbus service wrapper
|
||||
self._ps_ = None
|
||||
elif (not old and not len(old)) and (new and len(new)):
|
||||
# PS started up
|
||||
self._get_ps()
|
||||
|
||||
_ps_ = None
|
||||
def _get_ps(self):
|
||||
"""Retrieve dbus interface to PresenceService
|
||||
|
||||
Also registers for updates from various dbus events on the
|
||||
interface.
|
||||
|
||||
If unable to retrieve the interface, we will temporarily
|
||||
return an _OfflineInterface object to allow the calling
|
||||
code to continue functioning as though it had accessed a
|
||||
real presence service.
|
||||
|
||||
If successful, caches the presence service interface
|
||||
for use by other methods and returns that interface
|
||||
"""
|
||||
if not self._ps_:
|
||||
try:
|
||||
# NOTE: We need to follow_name_owner_changes here
|
||||
# because we can not connect to a signal unless
|
||||
# we follow the changes or we start the service
|
||||
# before we connect. Starting the service here
|
||||
# causes a major bottleneck during startup
|
||||
ps = dbus.Interface(
|
||||
self._bus.get_object(DBUS_SERVICE,
|
||||
DBUS_PATH,
|
||||
follow_name_owner_changes=True),
|
||||
DBUS_INTERFACE
|
||||
)
|
||||
except dbus.exceptions.DBusException, err:
|
||||
_logger.error(
|
||||
"""Failure retrieving %r interface from the D-BUS service %r %r: %s""",
|
||||
DBUS_INTERFACE, DBUS_SERVICE, DBUS_PATH, err
|
||||
)
|
||||
if self._allow_offline_iface:
|
||||
return _OfflineInterface()
|
||||
raise RuntimeError("Failed to connect to the presence service.")
|
||||
else:
|
||||
self._ps_ = ps
|
||||
ps.connect_to_signal('BuddyAppeared', self._buddy_appeared_cb)
|
||||
ps.connect_to_signal('BuddyDisappeared', self._buddy_disappeared_cb)
|
||||
ps.connect_to_signal('ActivityAppeared', self._activity_appeared_cb)
|
||||
ps.connect_to_signal('ActivityDisappeared', self._activity_disappeared_cb)
|
||||
ps.connect_to_signal('ActivityInvitation', self._activity_invitation_cb)
|
||||
ps.connect_to_signal('PrivateInvitation', self._private_invitation_cb)
|
||||
return self._ps_
|
||||
|
||||
_ps = property(
|
||||
_get_ps, None, None,
|
||||
"""DBUS interface to the PresenceService (services/presence/presenceservice)"""
|
||||
)
|
||||
|
||||
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. The point is to only have
|
||||
_one_ Python object for any D-Bus object represented by an object path,
|
||||
effectively wrapping the D-Bus object in a single Python GObject.
|
||||
|
||||
returns presence Buddy or Activity representation
|
||||
"""
|
||||
obj = None
|
||||
try:
|
||||
obj = self._objcache[object_path]
|
||||
except KeyError:
|
||||
if object_path.startswith(self._PS_BUDDY_OP):
|
||||
obj = Buddy(self._bus, self._new_object,
|
||||
self._del_object, object_path)
|
||||
elif object_path.startswith(self._PS_ACTIVITY_OP):
|
||||
obj = Activity(self._bus, self._new_object,
|
||||
self._del_object, object_path)
|
||||
try:
|
||||
# Pre-fill the activity's ID
|
||||
foo = obj.props.id
|
||||
except dbus.exceptions.DBusException, err:
|
||||
pass
|
||||
else:
|
||||
raise RuntimeError("Unknown object type")
|
||||
self._objcache[object_path] = obj
|
||||
return obj
|
||||
|
||||
def _have_object(self, object_path):
|
||||
return object_path in self._objcache.keys()
|
||||
|
||||
def _del_object(self, object_path):
|
||||
"""Fully remove an object from the object cache when it's no longer needed.
|
||||
"""
|
||||
del self._objcache[object_path]
|
||||
|
||||
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"""
|
||||
# Don't try to create a new object here if needed; it will probably
|
||||
# fail anyway because the object has already been destroyed in the PS
|
||||
if self._have_object(object_path):
|
||||
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, activity_path, buddy_path,
|
||||
message):
|
||||
"""Emit GObject event with presence.activity.Activity object"""
|
||||
self.emit('activity-invitation', self._new_object(activity_path),
|
||||
self._new_object(buddy_path), unicode(message))
|
||||
return False
|
||||
|
||||
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, activity_path,
|
||||
buddy_path, message)
|
||||
|
||||
def _emit_private_invitation_signal(self, bus_name, connection, channel):
|
||||
"""Emit GObject event with bus_name, connection and channel"""
|
||||
self.emit('private-invitation', 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):
|
||||
"""Return the Buddy or Activity object corresponding to the given
|
||||
D-Bus object path.
|
||||
"""
|
||||
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)
|
||||
"""
|
||||
try:
|
||||
resp = self._ps.GetActivities()
|
||||
except dbus.exceptions.DBusException, err:
|
||||
_logger.warn(
|
||||
"""Unable to retrieve activity list from presence service: %s"""
|
||||
% err
|
||||
)
|
||||
return []
|
||||
else:
|
||||
acts = []
|
||||
for item in resp:
|
||||
acts.append(self._new_object(item))
|
||||
return acts
|
||||
|
||||
def _get_activities_cb(self, reply_handler, resp):
|
||||
acts = []
|
||||
for item in resp:
|
||||
acts.append(self._new_object(item))
|
||||
|
||||
reply_handler(acts)
|
||||
|
||||
def _get_activities_error_cb(self, error_handler, e):
|
||||
if error_handler:
|
||||
error_handler(e)
|
||||
else:
|
||||
_logger.warn(
|
||||
"""Unable to retrieve activity-list from presence service: %s"""
|
||||
% e
|
||||
)
|
||||
|
||||
def get_activities_async(self, reply_handler=None, error_handler=None):
|
||||
"""Retrieve set of all activities from service asyncronously
|
||||
"""
|
||||
|
||||
if not reply_handler:
|
||||
logging.error('Function get_activities_async called without a reply handler. Can not run.')
|
||||
return
|
||||
|
||||
self._ps.GetActivities(
|
||||
reply_handler=lambda resp:self._get_activities_cb(reply_handler, resp),
|
||||
error_handler=lambda e:self._get_activities_error_cb(error_handler, e))
|
||||
|
||||
|
||||
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, err:
|
||||
_logger.warn(
|
||||
"""Unable to retrieve activity handle for %r from presence service: %s"""
|
||||
% (activity_id, err)
|
||||
)
|
||||
return None
|
||||
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)
|
||||
"""
|
||||
try:
|
||||
resp = self._ps.GetBuddies()
|
||||
except dbus.exceptions.DBusException, err:
|
||||
_logger.warn(
|
||||
"""Unable to retrieve buddy-list from presence service: %s"""
|
||||
% err
|
||||
)
|
||||
return []
|
||||
else:
|
||||
buddies = []
|
||||
for item in resp:
|
||||
buddies.append(self._new_object(item))
|
||||
return buddies
|
||||
|
||||
def _get_buddies_cb(self, reply_handler, resp):
|
||||
buddies = []
|
||||
for item in resp:
|
||||
buddies.append(self._new_object(item))
|
||||
|
||||
reply_handler(buddies)
|
||||
|
||||
def _get_buddies_error_cb(self, error_handler, e):
|
||||
if error_handler:
|
||||
error_handler(e)
|
||||
else:
|
||||
_logger.warn(
|
||||
"""Unable to retrieve buddy-list from presence service: %s"""
|
||||
% e
|
||||
)
|
||||
|
||||
def get_buddies_async(self, reply_handler=None, error_handler=None):
|
||||
"""Retrieve set of all buddies from service asyncronously
|
||||
"""
|
||||
|
||||
if not reply_handler:
|
||||
logging.error('Function get_buddies_async called without a reply handler. Can not run.')
|
||||
return
|
||||
|
||||
self._ps.GetBuddies(
|
||||
reply_handler=lambda resp:self._get_buddies_cb(reply_handler, resp),
|
||||
error_handler=lambda e:self._get_buddies_error_cb(error_handler, e))
|
||||
|
||||
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, err:
|
||||
_logger.warn(
|
||||
"""Unable to retrieve buddy handle for %r from presence service: %s"""
|
||||
% key, err
|
||||
)
|
||||
return None
|
||||
return self._new_object(buddy_op)
|
||||
|
||||
def get_buddy_by_telepathy_handle(self, tp_conn_name, tp_conn_path,
|
||||
handle):
|
||||
"""Retrieve single Buddy object for the given public key
|
||||
|
||||
:Parameters:
|
||||
`tp_conn_name` : str
|
||||
The well-known bus name of a Telepathy connection
|
||||
`tp_conn_path` : dbus.ObjectPath
|
||||
The object path of the Telepathy connection
|
||||
`handle` : int or long
|
||||
The handle of a Telepathy contact on that connection,
|
||||
of type HANDLE_TYPE_CONTACT. This may not be a
|
||||
channel-specific handle.
|
||||
:Returns: the Buddy object, or None if the buddy is not found
|
||||
"""
|
||||
try:
|
||||
buddy_op = self._ps.GetBuddyByTelepathyHandle(tp_conn_name,
|
||||
tp_conn_path,
|
||||
handle)
|
||||
except dbus.exceptions.DBusException, err:
|
||||
_logger.warn('Unable to retrieve buddy handle for handle %u at '
|
||||
'conn %s:%s from presence service: %s',
|
||||
handle, tp_conn_name, tp_conn_path, err)
|
||||
return None
|
||||
return self._new_object(buddy_op)
|
||||
|
||||
def get_owner(self):
|
||||
"""Retrieves the laptop "owner" Buddy object."""
|
||||
try:
|
||||
owner_op = self._ps.GetOwner()
|
||||
except dbus.exceptions.DBusException, err:
|
||||
_logger.warn(
|
||||
"""Unable to retrieve local user/owner from presence service: %s"""
|
||||
% err
|
||||
)
|
||||
raise RuntimeError("Could not get owner object from presence service.")
|
||||
return self._new_object(owner_op)
|
||||
|
||||
def _share_activity_cb(self, activity, psact):
|
||||
"""Notify with GObject event of successful sharing of activity
|
||||
"""
|
||||
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))
|
||||
self.emit("activity-shared", False, None, err)
|
||||
|
||||
def share_activity(self, activity, properties={}, private=True):
|
||||
"""Ask presence service to ask the activity to share itself publicly.
|
||||
|
||||
Uses the AdvertiseActivity 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()
|
||||
|
||||
# Ensure the activity is not already shared/joined
|
||||
for obj in self._objcache.values():
|
||||
if not isinstance(object, Activity):
|
||||
continue
|
||||
if obj.props.id == actid or obj.props.joined:
|
||||
raise RuntimeError("Activity %s is already shared." %
|
||||
actid)
|
||||
|
||||
atype = activity.get_bundle_id()
|
||||
name = activity.props.title
|
||||
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
|
||||
should use when talking directly to telepathy
|
||||
|
||||
returns the bus name and the object path of the Telepathy connection"""
|
||||
|
||||
try:
|
||||
bus_name, object_path = self._ps.GetPreferredConnection()
|
||||
except dbus.exceptions.DBusException:
|
||||
return None
|
||||
|
||||
return bus_name, object_path
|
||||
|
||||
class _OfflineInterface( object ):
|
||||
"""Offline-presence-service interface
|
||||
|
||||
Used to mimic the behaviour of a real PresenceService sufficiently
|
||||
to avoid crashing client code that expects the given interface.
|
||||
|
||||
XXX we could likely return a "MockOwner" object reasonably
|
||||
easily, but would it be worth it?
|
||||
"""
|
||||
def raiseException( self, *args, **named ):
|
||||
"""Raise dbus.exceptions.DBusException"""
|
||||
raise dbus.exceptions.DBusException(
|
||||
"""PresenceService Interface not available"""
|
||||
)
|
||||
GetActivities = raiseException
|
||||
GetActivityById = raiseException
|
||||
GetBuddies = raiseException
|
||||
GetBuddyByPublicKey = raiseException
|
||||
GetOwner = raiseException
|
||||
GetPreferredConnection = raiseException
|
||||
def ShareActivity(
|
||||
self, actid, atype, name, properties,
|
||||
reply_handler, error_handler,
|
||||
):
|
||||
"""Pretend to share and fail..."""
|
||||
exc = IOError(
|
||||
"""Unable to share activity as PresenceService is not currenly available"""
|
||||
)
|
||||
return error_handler( exc )
|
||||
|
||||
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])),
|
||||
'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'activity-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'private-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
|
||||
gobject.TYPE_PYOBJECT])),
|
||||
'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT]))
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
gobject.GObject.__init__(self)
|
||||
|
||||
def get_activities(self):
|
||||
return []
|
||||
|
||||
def get_activity(self, activity_id):
|
||||
return None
|
||||
|
||||
def get_buddies(self):
|
||||
return []
|
||||
|
||||
def get_buddy(self, key):
|
||||
return None
|
||||
|
||||
def get_owner(self):
|
||||
return None
|
||||
|
||||
def share_activity(self, activity, properties={}):
|
||||
return None
|
||||
|
||||
_ps = None
|
||||
def get_instance(allow_offline_iface=False):
|
||||
"""Retrieve this process' view of the PresenceService"""
|
||||
global _ps
|
||||
if not _ps:
|
||||
_ps = PresenceService(allow_offline_iface)
|
||||
return _ps
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
This is a test of presence.
|
||||
|
||||
To test this service we will start up a mock dbus library:
|
||||
|
||||
>>> from sugar.testing import mockdbus
|
||||
>>> import dbus
|
||||
>>> pres_service = mockdbus.MockService(
|
||||
... 'org.laptop.Presence', '/org/laptop/Presence', name='pres')
|
||||
>>> pres_service.install()
|
||||
>>> pres_interface = dbus.Interface(pres_service, 'org.laptop.Presence')
|
||||
|
||||
Then we import the library (second, to make sure it connects to our
|
||||
mocked system, though the lazy instantiation in get_instance() should
|
||||
handle it):
|
||||
|
||||
>>> from sugar.presence import PresenceService
|
||||
>>> ps = PresenceService.get_instance()
|
||||
>>> pres_interface.make_response('getServices', [])
|
||||
>>> ps.get_services()
|
||||
Called pres.org.laptop.Presence:getServices()
|
||||
[]
|
||||
>>> pres_interface.make_response('getBuddies', [])
|
||||
>>> ps.get_buddies()
|
||||
Called pres.org.laptop.Presence:getBuddies()
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
# This should eventually land in telepathy-python, so has the same license:
|
||||
|
||||
# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
__all__ = ('TubeConnection',)
|
||||
__docformat__ = 'reStructuredText'
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from dbus.connection import Connection
|
||||
|
||||
|
||||
logger = logging.getLogger('telepathy.tubeconn')
|
||||
|
||||
|
||||
class TubeConnection(Connection):
|
||||
|
||||
def __new__(cls, conn, tubes_iface, tube_id, address=None,
|
||||
group_iface=None, mainloop=None):
|
||||
if address is None:
|
||||
address = tubes_iface.GetDBusTubeAddress(tube_id)
|
||||
self = super(TubeConnection, cls).__new__(cls, address,
|
||||
mainloop=mainloop)
|
||||
|
||||
self._tubes_iface = tubes_iface
|
||||
self.tube_id = tube_id
|
||||
self.participants = {}
|
||||
self.bus_name_to_handle = {}
|
||||
self._mapping_watches = []
|
||||
|
||||
if group_iface is None:
|
||||
method = conn.GetSelfHandle
|
||||
else:
|
||||
method = group_iface.GetSelfHandle
|
||||
method(reply_handler=self._on_get_self_handle_reply,
|
||||
error_handler=self._on_get_self_handle_error)
|
||||
|
||||
return self
|
||||
|
||||
def _on_get_self_handle_reply(self, handle):
|
||||
self.self_handle = handle
|
||||
match = self._tubes_iface.connect_to_signal('DBusNamesChanged',
|
||||
self._on_dbus_names_changed)
|
||||
self._tubes_iface.GetDBusNames(self.tube_id,
|
||||
reply_handler=self._on_get_dbus_names_reply,
|
||||
error_handler=self._on_get_dbus_names_error)
|
||||
self._dbus_names_changed_match = match
|
||||
|
||||
def _on_get_self_handle_error(self, e):
|
||||
logging.basicConfig()
|
||||
logger.error('GetSelfHandle failed: %s', e)
|
||||
|
||||
def close(self):
|
||||
self._dbus_names_changed_match.remove()
|
||||
self._on_dbus_names_changed(self.tube_id, (), self.participants.keys())
|
||||
super(TubeConnection, self).close()
|
||||
|
||||
def _on_get_dbus_names_reply(self, names):
|
||||
self._on_dbus_names_changed(self.tube_id, names, ())
|
||||
|
||||
def _on_get_dbus_names_error(self, e):
|
||||
logging.basicConfig()
|
||||
logger.error('GetDBusNames failed: %s', e)
|
||||
|
||||
def _on_dbus_names_changed(self, tube_id, added, removed):
|
||||
if tube_id == self.tube_id:
|
||||
for handle, bus_name in added:
|
||||
if handle == self.self_handle:
|
||||
# I've just joined - set my unique name
|
||||
self.set_unique_name(bus_name)
|
||||
self.participants[handle] = bus_name
|
||||
self.bus_name_to_handle[bus_name] = handle
|
||||
|
||||
# call the callback while the removed people are still in
|
||||
# participants, so their bus names are available
|
||||
for callback in self._mapping_watches:
|
||||
callback(added, removed)
|
||||
|
||||
for handle in removed:
|
||||
bus_name = self.participants.pop(handle, None)
|
||||
self.bus_name_to_handle.pop(bus_name, None)
|
||||
|
||||
def watch_participants(self, callback):
|
||||
self._mapping_watches.append(callback)
|
||||
if self.participants:
|
||||
# GetDBusNames already returned: fake a participant add event
|
||||
# immediately
|
||||
added = []
|
||||
for k, v in self.participants.iteritems():
|
||||
added.append((k, v))
|
||||
callback(added, [])
|
||||
Reference in New Issue
Block a user