Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2007-06-12 02:36:31 (GMT)
committer Simon McVittie <simon.mcvittie@collabora.co.uk>2007-06-12 02:36:31 (GMT)
commit3ffe8b89e6d495519f03afd981ade616dc3c2430 (patch)
treed6a439d1807b5ef9d3663e49727d5f77caa9935d
parent7ff6a4ceb5f1570ba490d3c0d644e23c11ad8d86 (diff)
Hook up Salut to the presence service
-rw-r--r--src/buddy.py31
-rw-r--r--src/linklocal_plugin.py146
-rw-r--r--src/presenceservice.py65
-rw-r--r--src/server_plugin.py30
-rw-r--r--src/telepathy_plugin.py58
5 files changed, 240 insertions, 90 deletions
diff --git a/src/buddy.py b/src/buddy.py
index d0ad63d..d225cc8 100644
--- a/src/buddy.py
+++ b/src/buddy.py
@@ -667,12 +667,22 @@ class GenericOwner(Buddy):
def _set_self_olpc_properties(self, tp):
conn = tp.get_connection()
# FIXME: omit color/key/ip4-address if None?
- conn[CONN_INTERFACE_BUDDY_INFO].SetProperties(
- {'color': self._color or '', 'key': self._key or '',
- 'ip4-address': self._ip4_address or '' },
+
+ props = dbus.Dictionary({
+ 'color': self._color or '',
+ 'key': dbus.ByteArray(self._key or ''),
+ 'ip4-address': self._ip4_address or '',
+ }, signature='sv')
+
+ # FIXME: clarify whether we're meant to support random extra properties
+ # (Salut doesn't)
+ if tp._PROTOCOL == 'salut':
+ del props['ip4-address']
+
+ conn[CONN_INTERFACE_BUDDY_INFO].SetProperties(props,
reply_handler=_noop,
error_handler=lambda e:
- _logger.warning('Error setting alias: %s', e))
+ _logger.warning('Error setting OLPC properties: %s', e))
# Hack so we can use this as a timeout handler
return False
@@ -723,8 +733,17 @@ class GenericOwner(Buddy):
_logger.debug("server does not accept JPEG format avatars.")
return
- img_data = _get_buddy_icon_at_size(icon_data, min(maxw, 96),
- min(maxh, 96), maxsize)
+ width = 96
+ height = 96
+ size = 8192
+ if maxw > 0 and width > maxw:
+ width = maxw
+ if maxw > 0 and height > maxh:
+ height = maxh
+ if maxsize > 0 and size > maxsize:
+ size = maxsize
+
+ img_data = _get_buddy_icon_at_size(icon_data, width, height, size)
conn[CONN_INTERFACE_AVATARS].SetAvatar(img_data, "image/jpeg",
reply_handler=set_self_avatar_cb,
error_handler=lambda e:
diff --git a/src/linklocal_plugin.py b/src/linklocal_plugin.py
index b8f6445..7d2073c 100644
--- a/src/linklocal_plugin.py
+++ b/src/linklocal_plugin.py
@@ -1,3 +1,4 @@
+"""Link-local plugin for Presence Service"""
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd.
#
@@ -15,13 +16,146 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+# Standard library
+import logging
+from itertools import izip
+from string import hexdigits
+
+# Other libraries
import gobject
+from telepathy.client import (ConnectionManager, Connection)
+from telepathy.interfaces import (CONN_MGR_INTERFACE, CONN_INTERFACE,
+ CHANNEL_INTERFACE_GROUP)
+from telepathy.constants import (HANDLE_TYPE_CONTACT,
+ CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED,
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES)
+
+# Presence Service local modules
+import psutils
+from telepathy_plugin import TelepathyPlugin
+
+
+CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+
+_logger = logging.getLogger('s-p-s.linklocal_plugin')
+
+
+class LinkLocalPlugin(TelepathyPlugin):
+ """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.
+ """
+
+ _TP_CONN_MANAGER = 'salut'
+ _PROTOCOL = 'salut'
+ _OBJ_PATH_PREFIX = "/org/freedesktop/Telepathy/Connection/salut/salut/"
-class LinkLocalPlugin(gobject.GObject):
def __init__(self, registry, owner):
- gobject.GObject.__init__(self)
- self._registry = registry
- self._owner = owner
+ TelepathyPlugin.__init__(self, registry, owner)
+
+ def _get_account_info(self):
+ """Retrieve connection manager parameters for this account
+ """
+ server = self._owner.get_server()
+ khash = psutils.pubkey_to_keyid(self._owner.props.key)
+
+ return {
+ 'nickname': '%s' % self._owner.props.nick,
+ 'first-name': ' ',
+ 'last-name': '%s' % self._owner.props.nick,
+ 'jid': '%s@%s' % (khash, server),
+ 'published-name': '%s' % self._owner.props.nick,
+ }
+
+ 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
+ """
+ # Search existing connections, if any, that we might be able to use
+ connections = Connection.get_connections()
+ for item in connections:
+ if not item.object_path.startswith(self._OBJ_PATH_PREFIX):
+ continue
+ if item[CONN_INTERFACE].GetProtocol() != self._PROTOCOL:
+ continue
+ # Any Salut instance will do
+ return item
+ return None
+
+ def identify_contacts(self, tp_chan, handles, identifiers=None):
+ """Work out the "best" unique identifier we can for the given handles,
+ in the context of the given channel (which may be None), using only
+ 'fast' connection manager API (that does not involve network
+ round-trips).
+
+ For the XMPP server case, we proceed as follows:
+
+ * Find the owners of the given handles, if the channel has
+ channel-specific handles
+ * If the owner (globally-valid JID) is on a trusted server, return
+ 'keyid/' plus the 'key fingerprint' (the user part of their JID,
+ currently implemented as the SHA-1 of the Base64 blob in
+ owner.key.pub)
+ * If the owner (globally-valid JID) cannot be found or is on an
+ untrusted server, return 'xmpp/' plus an escaped form of the JID
+
+ The idea is that we identify buddies by key-ID (i.e. by key, assuming
+ no collisions) if we can find it without making network round-trips,
+ but if that's not possible we just use their JIDs.
+
+ :Parameters:
+ `tp_chan` : telepathy.client.Channel or None
+ The channel in which the handles were found, or None if they
+ are known to be channel-specific handles
+ `handles` : iterable over (int or long)
+ The contacts' handles in that channel
+ :Returns:
+ A dict mapping the provided handles to the best available
+ unique identifier, which is a string that could be used as a
+ suffix to an object path
+ """
+ # we need to be able to index into handles, so force them to
+ # be a sequence
+ if not isinstance(handles, (tuple, list)):
+ handles = tuple(handles)
+
+ # we happen to know that Salut has no channel-specific handles
+
+ if identifiers is None:
+ identifiers = self._conn[CONN_INTERFACE].InspectHandles(
+ HANDLE_TYPE_CONTACT, handles)
+
+ ret = {}
+ for handle, ident in izip(handles, identifiers):
+ # special-case the Owner - we always know who we are
+ if handle == self.self_handle:
+ ret[handle] = self._owner.props.objid
+ continue
+
+ # we also happen to know that on Salut, getting properties
+ # is immediate, and the key is (well, will be) trustworthy
+
+ if CONN_INTERFACE_BUDDY_INFO in self._conn:
+ props = self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(
+ handle)
+ key = props.get('key')
+ else:
+ key = None
+
+ if key is not None:
+ khash = psutils.pubkey_to_keyid(key)
+ ret[handle] = 'keyid/' + khash
+ else:
+ ret[handle] = 'salut/' + psutils.escape_identifier(ident)
- def cleanup(self):
- pass
+ return ret
diff --git a/src/presenceservice.py b/src/presenceservice.py
index be58407..bd87a51 100644
--- a/src/presenceservice.py
+++ b/src/presenceservice.py
@@ -58,18 +58,12 @@ class NotFoundError(DBusException):
class PresenceService(ExportedGObject):
__gtype_name__ = "PresenceService"
- __gsignals__ = {
- 'connection-status': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_BOOLEAN]))
- }
-
def _create_owner(self):
# Overridden by TestPresenceService
return ShellOwner(self, self._session_bus)
def __init__(self):
self._next_object_id = 0
- self._connected = False
# all Buddy objects
# identifier -> Buddy, GC'd when no more refs exist
@@ -106,27 +100,27 @@ class PresenceService(ExportedGObject):
self._registry = ManagerRegistry()
self._registry.LoadManagers()
- # Set up the server connection
+ # Set up the Telepathy plugins
self._server_plugin = ServerPlugin(self._registry, self._owner)
- self._handles_buddies[self._server_plugin] = {}
- self._activities_by_handle[self._server_plugin] = {}
-
- self._server_plugin.connect('status', self._server_status_cb)
- self._server_plugin.connect('contacts-online', self._contacts_online)
- self._server_plugin.connect('contacts-offline', self._contacts_offline)
- self._server_plugin.connect('activity-invitation',
- self._activity_invitation)
- self._server_plugin.connect('private-invitation',
- self._private_invitation)
- self._server_plugin.start()
+ self._ll_plugin = LinkLocalPlugin(self._registry, self._owner)
+ self._plugins = [self._server_plugin, self._ll_plugin]
+ self._connected_plugins = set()
+
+ for tp in self._plugins:
+ self._handles_buddies[tp] = {}
+ self._activities_by_handle[tp] = {}
+
+ tp.connect('status', self._tp_status_cb)
+ tp.connect('contacts-online', self._contacts_online)
+ tp.connect('contacts-offline', self._contacts_offline)
+ tp.connect('activity-invitation',
+ self._activity_invitation)
+ tp.connect('private-invitation',
+ self._private_invitation)
+ tp.start()
self._contacts_online_queue = []
- # Set up the link local connection
- self._ll_plugin = LinkLocalPlugin(self._registry, self._owner)
- self._handles_buddies[self._ll_plugin] = {}
- self._activities_by_handle[self._ll_plugin] = {}
-
ExportedGObject.__init__(self, self._session_bus, _PRESENCE_PATH)
# for activation to work in a race-free way, we should really
@@ -143,21 +137,14 @@ class PresenceService(ExportedGObject):
"""Log event when D-Bus kicks us off the bus for some reason"""
_logger.debug("Disconnected from session bus!!!")
- def _server_status_cb(self, plugin, status, reason):
-
- # FIXME: figure out connection status when we have a salut plugin too
- old_status = self._connected
+ def _tp_status_cb(self, plugin, status, reason):
if status == CONNECTION_STATUS_CONNECTED:
- self._connected = True
self._tp_connected(plugin)
else:
- self._connected = False
self._tp_disconnected(plugin)
- if self._connected != old_status:
- self.emit('connection-status', self._connected)
-
def _tp_connected(self, tp):
+ self._connected_plugins.add(tp)
self._handles_buddies[tp][tp.self_handle] = self._owner
self._owner.add_telepathy_handle(tp, tp.self_handle,
tp.self_identifier)
@@ -235,6 +222,7 @@ class PresenceService(ExportedGObject):
conn.object_path)
def _tp_disconnected(self, tp):
+ self._connected_plugins.discard(tp)
if tp.self_handle is not None:
self._handles_buddies.setdefault(tp, {}).pop(
tp.self_handle, None)
@@ -690,10 +678,19 @@ class PresenceService(ExportedGObject):
self._share_activity(actid, atype, name, properties,
async_cb, async_err_cb)
+ def _get_preferred_plugin(self):
+ for tp in self._plugins:
+ if tp in self._connected_plugins:
+ return tp
+ return None
+
@dbus.service.method(_PRESENCE_INTERFACE,
in_signature='', out_signature="so")
def GetPreferredConnection(self):
- conn = self._server_plugin.get_connection()
+ tp = self._get_preferred_plugin
+ if tp is None:
+ raise NotFoundError('No connection is available')
+ conn = tp.get_connection()
return str(conn.service_name), conn.object_path
def cleanup(self):
@@ -706,7 +703,7 @@ class PresenceService(ExportedGObject):
# FIXME check which tp client we should use to share the activity
color = self._owner.props.color
activity = Activity(self._session_bus, objid, self,
- self._server_plugin, 0,
+ self._get_preferred_plugin(), 0,
id=actid, type=atype,
name=name, color=color, local=True)
activity.connect("validity-changed",
diff --git a/src/server_plugin.py b/src/server_plugin.py
index 94d1a89..7c6bdd3 100644
--- a/src/server_plugin.py
+++ b/src/server_plugin.py
@@ -1,4 +1,4 @@
-"""Telepathy-python presence server interface/implementation plugin"""
+"""XMPP server plugin for Presence Service"""
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd.
#
@@ -35,8 +35,6 @@ import psutils
from telepathy_plugin import TelepathyPlugin
-_PROTOCOL = "jabber"
-_OBJ_PATH_PREFIX = "/org/freedesktop/Telepathy/Connection/gabble/jabber/"
_logger = logging.getLogger('s-p-s.server_plugin')
@@ -50,6 +48,10 @@ class ServerPlugin(TelepathyPlugin):
to implement the PresenceService.
"""
+ _TP_CONN_MANAGER = 'gabble'
+ _PROTOCOL = 'jabber'
+ _OBJ_PATH_PREFIX = "/org/freedesktop/Telepathy/Connection/gabble/jabber/"
+
def __init__(self, registry, owner):
TelepathyPlugin.__init__(self, registry, owner)
@@ -98,9 +100,9 @@ class ServerPlugin(TelepathyPlugin):
# Search existing connections, if any, that we might be able to use
connections = Connection.get_connections()
for item in connections:
- if not item.object_path.startswith(_OBJ_PATH_PREFIX):
+ if not item.object_path.startswith(self._OBJ_PATH_PREFIX):
continue
- if item[CONN_INTERFACE].GetProtocol() != _PROTOCOL:
+ if item[CONN_INTERFACE].GetProtocol() != self._PROTOCOL:
continue
if item[CONN_INTERFACE].GetStatus() == CONNECTION_STATUS_CONNECTED:
test_handle = item[CONN_INTERFACE].RequestHandles(
@@ -110,20 +112,16 @@ class ServerPlugin(TelepathyPlugin):
return item
return None
- def _make_new_connection(self):
- acct = self._account.copy()
-
- # Create a new connection
- gabble_mgr = self._registry.GetManager('gabble')
- name, path = gabble_mgr[CONN_MGR_INTERFACE].RequestConnection(
- _PROTOCOL, acct)
- conn = Connection(name, path)
- del acct
- return conn
-
def _could_connect(self):
return bool(self._ip4am.props.address)
+ def _connected_cb(self):
+ if self._account['register']:
+ # we successfully register this account
+ self._owner.set_registered(True)
+
+ TelepathyPlugin._connected_cb(self)
+
def _server_is_trusted(self, hostname):
"""Return True if the server with the given hostname is trusted to
verify public-key ownership correctly, and only allows users to
diff --git a/src/telepathy_plugin.py b/src/telepathy_plugin.py
index 330b74c..0e75d62 100644
--- a/src/telepathy_plugin.py
+++ b/src/telepathy_plugin.py
@@ -22,7 +22,7 @@ from itertools import izip
import gobject
-from telepathy.client import Channel
+from telepathy.client import (Channel, Connection)
from telepathy.constants import (CONNECTION_STATUS_DISCONNECTED,
CONNECTION_STATUS_CONNECTING, CONNECTION_STATUS_CONNECTED,
CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED,
@@ -31,14 +31,15 @@ from telepathy.constants import (CONNECTION_STATUS_DISCONNECTED,
from telepathy.interfaces import (CONN_INTERFACE, CHANNEL_TYPE_TEXT,
CHANNEL_TYPE_STREAMED_MEDIA, CHANNEL_INTERFACE_GROUP,
CONN_INTERFACE_PRESENCE, CONN_INTERFACE_AVATARS,
- CONN_INTERFACE_ALIASING, CHANNEL_TYPE_CONTACT_LIST)
+ CONN_INTERFACE_ALIASING, CHANNEL_TYPE_CONTACT_LIST,
+ CONN_MGR_INTERFACE)
CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
-_logger = logging.getLogger('s-p-s.server_plugin')
+_logger = logging.getLogger('s-p-s.telepathy_plugin')
class TelepathyPlugin(gobject.GObject):
@@ -71,6 +72,8 @@ class TelepathyPlugin(gobject.GObject):
}
_RECONNECT_TIMEOUT = 5000
+ _TP_CONN_MANAGER = 'gabble'
+ _PROTOCOL = 'jabber'
def __init__(self, registry, owner):
"""Initialize the ServerPlugin instance
@@ -195,9 +198,9 @@ class TelepathyPlugin(gobject.GObject):
if status == CONNECTION_STATUS_DISCONNECTED:
def connect_reply():
- _logger.debug('Connect() succeeded')
+ _logger.debug('%r: Connect() succeeded', self)
def connect_error(e):
- _logger.debug('Connect() failed: %s', e)
+ _logger.debug('%r: Connect() failed: %s', self, e)
if not self._reconnect_id:
self._reconnect_id = gobject.timeout_add(self._RECONNECT_TIMEOUT,
self._reconnect_cb)
@@ -212,7 +215,15 @@ class TelepathyPlugin(gobject.GObject):
raise NotImplementedError
def _make_new_connection(self):
- raise NotImplementedError
+ acct = self._account.copy()
+
+ # Create a new connection
+ mgr = self._registry.GetManager(self._TP_CONN_MANAGER)
+ name, path = mgr[CONN_MGR_INTERFACE].RequestConnection(
+ self._PROTOCOL, acct)
+ conn = Connection(name, path)
+ del acct
+ return conn
def _handle_connection_status_change(self, status, reason):
if status == self._conn_status:
@@ -220,17 +231,14 @@ class TelepathyPlugin(gobject.GObject):
if status == CONNECTION_STATUS_CONNECTING:
self._conn_status = status
- _logger.debug("status: connecting...")
+ _logger.debug("%r: connecting...", self)
elif status == CONNECTION_STATUS_CONNECTED:
- if self._connected_cb():
- _logger.debug("status: connected")
- self._conn_status = status
- else:
- self.cleanup()
- _logger.debug("status: was connected, but an error occurred")
+ self._conn_status = status
+ _logger.debug("%r: connected", self)
+ self._connected_cb()
elif status == CONNECTION_STATUS_DISCONNECTED:
self.cleanup()
- _logger.debug("status: disconnected (reason %r)", reason)
+ _logger.debug("%r: disconnected (reason %r)", self, reason)
if reason == CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED:
# FIXME: handle connection failure; retry later?
pass
@@ -245,9 +253,6 @@ class TelepathyPlugin(gobject.GObject):
self.emit('status', self._conn_status, int(reason))
- def _connected_cb(self):
- raise NotImplementedError
-
def _could_connect(self):
return True
@@ -277,7 +282,7 @@ class TelepathyPlugin(gobject.GObject):
def _contacts_offline(self, handles):
"""Handle contacts going offline (send message, update set)"""
self._online_contacts -= handles
- _logger.debug('Contacts now offline: %r', handles)
+ _logger.debug('%r: Contacts now offline: %r', self, handles)
self.emit("contacts-offline", handles)
def _contacts_online(self, handles):
@@ -308,7 +313,7 @@ class TelepathyPlugin(gobject.GObject):
objids.append(handle_to_objid[handle])
self._online_contacts |= frozenset(relevant)
- _logger.debug('Contacts now online:')
+ _logger.debug('%r: Contacts now online:', self)
for handle, objid in izip(relevant, objids):
_logger.debug(' %u .../%s', handle, objid)
self.emit('contacts-online', objids, relevant, jids)
@@ -333,8 +338,8 @@ class TelepathyPlugin(gobject.GObject):
self._subscribe_remote_pending -= affected
self._subscribe_remote_pending |= remote_pending
- def _publish_members_changed_cb(self, added, removed, local_pending,
- remote_pending, actor, reason):
+ def _publish_members_changed_cb(self, message, added, removed,
+ local_pending, remote_pending, actor, reason):
if local_pending:
# accept all requested subscriptions
@@ -401,10 +406,8 @@ class TelepathyPlugin(gobject.GObject):
def _connected_cb(self):
"""Callback on successful connection to a server
"""
-
- if self._account['register']:
- # we successfully register this account
- self._owner.set_registered(True)
+ # FIXME: cope with CMs that lack some of the interfaces
+ # FIXME: cope with CMs with no 'publish' or 'subscribe'
# request both handles at the same time to reduce round-trips
pub_handle, sub_handle = self._conn[CONN_INTERFACE].RequestHandles(
@@ -450,7 +453,6 @@ class TelepathyPlugin(gobject.GObject):
# Request presence for everyone we're subscribed to
self._conn[CONN_INTERFACE_PRESENCE].RequestPresence(subscribe_handles)
- return True
def start(self):
"""Start up the Telepathy networking connections
@@ -464,7 +466,7 @@ class TelepathyPlugin(gobject.GObject):
_connect_reply_cb or _connect_error_cb
"""
- _logger.debug("Starting up...")
+ _logger.debug("%r: Starting up...", self)
if self._reconnect_id > 0:
gobject.source_remove(self._reconnect_id)
@@ -474,4 +476,4 @@ class TelepathyPlugin(gobject.GObject):
if self._could_connect():
self._init_connection()
else:
- _logger.debug('Postponing connection')
+ _logger.debug('%r: Postponing connection', self)