diff options
Diffstat (limited to 'services/presence/server_plugin.py')
-rw-r--r-- | services/presence/server_plugin.py | 1171 |
1 files changed, 0 insertions, 1171 deletions
diff --git a/services/presence/server_plugin.py b/services/presence/server_plugin.py deleted file mode 100644 index 548b41f..0000000 --- a/services/presence/server_plugin.py +++ /dev/null @@ -1,1171 +0,0 @@ -"""Telepathy-python presence server interface/implementation plugin""" -# Copyright (C) 2007, Red Hat, Inc. -# Copyright (C) 2007, Collabora Ltd. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# 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 -import os -import sys -from string import hexdigits -try: - # Python >= 2.5 - from hashlib import md5 -except ImportError: - from md5 import new as md5 - -# Other libraries -import dbus -import gobject -import gtk -from telepathy.client import (ConnectionManager, ManagerRegistry, Connection, - Channel) -from telepathy.interfaces import (CONN_MGR_INTERFACE, CONN_INTERFACE, - CHANNEL_TYPE_CONTACT_LIST, CHANNEL_INTERFACE_GROUP, - CONN_INTERFACE_ALIASING, CONN_INTERFACE_AVATARS, CONN_INTERFACE_PRESENCE, - CHANNEL_TYPE_TEXT, CHANNEL_TYPE_STREAMED_MEDIA, PROPERTIES_INTERFACE) -from telepathy.constants import (HANDLE_TYPE_CONTACT, - HANDLE_TYPE_LIST, HANDLE_TYPE_CONTACT, HANDLE_TYPE_ROOM, - CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED, - CONNECTION_STATUS_CONNECTING, - CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED, - CONNECTION_STATUS_REASON_NONE_SPECIFIED, - CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES, - PROPERTY_FLAG_WRITE) -from sugar import util - -# Presence Service local modules -from buddyiconcache import BuddyIconCache -import psutils - - -CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo' -CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties' - -_PROTOCOL = "jabber" -_OBJ_PATH_PREFIX = "/org/freedesktop/Telepathy/Connection/gabble/jabber/" - -_logger = logging.getLogger('s-p-s.server_plugin') - -_RECONNECT_TIMEOUT = 5000 - -def _buddy_icon_save_cb(buf, data): - data[0] += buf - return True - -def _get_buddy_icon_at_size(icon, maxw, maxh, maxsize): - loader = gtk.gdk.PixbufLoader() - loader.write(icon) - loader.close() - unscaled_pixbuf = loader.get_pixbuf() - del loader - - pixbuf = unscaled_pixbuf.scale_simple(maxw, maxh, gtk.gdk.INTERP_BILINEAR) - del unscaled_pixbuf - - data = [""] - quality = 90 - img_size = maxsize + 1 - while img_size > maxsize: - del data - data = [""] - pixbuf.save_to_callback(_buddy_icon_save_cb, "jpeg", - {"quality":"%d" % quality}, data) - quality -= 10 - img_size = len(data[0]) - del pixbuf - - if img_size > maxsize: - del data - raise RuntimeError("could not size image less than %d bytes" % maxsize) - - return str(data[0]) - - -class ServerPlugin(gobject.GObject): - """Telepathy-python-based presence server interface - - The ServerPlugin instance translates network events from - Telepathy Python into GObject events. It provides direct - python calls to perform the required network operations - to implement the PresenceService. - """ - __gsignals__ = { - 'contact-online': - # Contact has come online and we've discovered all their buddy - # properties. - # args: - # contact identification (based on key ID or JID): str - # contact handle: int or long - # dict {name: str => property: object} - (gobject.SIGNAL_RUN_FIRST, None, [str, object, object]), - 'contact-offline': - # Contact has gone offline. - # args: contact handle - (gobject.SIGNAL_RUN_FIRST, None, [object]), - 'status': - # Connection status changed. - # args: status, reason as for Telepathy StatusChanged - (gobject.SIGNAL_RUN_FIRST, None, [int, int]), - 'avatar-updated': - # Contact's avatar has changed - # args: contact handle: int; icon data: str - (gobject.SIGNAL_RUN_FIRST, None, [object, object]), - 'buddy-properties-changed': - # OLPC buddy properties changed; as for PropertiesChanged - # args: - # contact handle: int - # properties: dict {name: str => property: object} - # FIXME: are these all the properties or just those that changed? - (gobject.SIGNAL_RUN_FIRST, None, [object, object]), - 'buddy-activities-changed': - # OLPC activities changed - # args: - # contact handle: int - # activity IDs: list of str - (gobject.SIGNAL_RUN_FIRST, None, [object, object]), - 'activity-invitation': - # We were invited to join an activity - # args: activity ID: str - (gobject.SIGNAL_RUN_FIRST, None, [object]), - 'private-invitation': - # We were invited to join a chat or a media call - # args: channel object path - (gobject.SIGNAL_RUN_FIRST, None, [object]), - 'activity-properties-changed': - # An activity's properties changed; as for - # ActivityPropertiesChanged - # args: activity ID: str; properties: dict { str => object } - # FIXME: are these all the properties or just those that changed? - (gobject.SIGNAL_RUN_FIRST, None, [object, object]), - 'activity-shared': - # share_activity() succeeded - # args: - # activity ID: str - # channel: telepathy.client.Channel, or None on failure - # error: None, or Exception on failure - # userdata as passed to share_activity - (gobject.SIGNAL_RUN_FIRST, None, [object, object, object, object]), - 'activity-joined': - # join_activity() succeeded - # args: as for activity-shared - (gobject.SIGNAL_RUN_FIRST, None, [object, object, object, object]), - } - - def __init__(self, registry, owner): - """Initialize the ServerPlugin instance - - registry -- telepathy.client.ManagerRegistry from the - PresenceService, used to find the "gabble" connection - manager in this case... - owner -- presence.buddy.GenericOwner instance (normally a - presence.buddy.ShellOwner instance) - """ - gobject.GObject.__init__(self) - - self._conn = None - self._icon_cache = BuddyIconCache() - - self._registry = registry - self._online_contacts = {} # handle -> jid - - # activity id -> handle - self._activities = {} - # (activity_id, handle of the activity channel) - self._joined_activities = [] - - self._owner = owner - self._owner.connect("property-changed", - self._owner_property_changed_cb) - self._owner.connect("icon-changed", self._owner_icon_changed_cb) - self.self_handle = None - - self._account = self._get_account_info() - self._conn_status = CONNECTION_STATUS_DISCONNECTED - self._reconnect_id = 0 - - # Monitor IPv4 address as an indicator of the network connection - self._ip4am = psutils.IP4AddressMonitor.get_instance() - self._ip4am.connect('address-changed', self._ip4_address_changed_cb) - - self._publish_channel = None - self._subscribe_channel = None - self._subscribe_members = set() - self._subscribe_local_pending = set() - self._subscribe_remote_pending = set() - - def _ip4_address_changed_cb(self, ip4am, address): - _logger.debug("::: IP4 address now %s", address) - if address: - _logger.debug("::: valid IP4 address, conn_status %s", - self._conn_status) - if self._conn_status == CONNECTION_STATUS_DISCONNECTED: - _logger.debug("::: will connect") - self.start() - else: - _logger.debug("::: invalid IP4 address, will disconnect") - self.cleanup() - - def _owner_property_changed_cb(self, owner, properties): - """Local user's configuration properties have changed - - owner -- the Buddy object for the local user - properties -- set of updated properties - - calls: - - _set_self_current_activity current-activity - _set_self_alias nick - _set_self_olpc_properties color - - depending on which properties are present in the - set of properties. - """ - _logger.debug("Owner properties changed: %s", properties) - - if properties.has_key("current-activity"): - self._set_self_current_activity() - - if properties.has_key("nick"): - self._set_self_alias() - # Hack; send twice to make sure the server gets it - gobject.timeout_add(1000, self._set_self_alias) - - if properties.has_key("color") or properties.has_key("ip4-address"): - if self._conn_status == CONNECTION_STATUS_CONNECTED: - self._set_self_olpc_properties() - - def _owner_icon_changed_cb(self, owner, icon): - """Owner has changed their icon, forward to network""" - _logger.debug("Owner icon changed to size %d", len(str(icon))) - self._set_self_avatar(icon) - - def _get_account_info(self): - """Retrieve metadata dictionary describing this account - - returns dictionary with: - - server : server url from owner - account : printable-ssh-key-hash@server - password : ssh-key-hash - register : whether to register (i.e. whether not yet - registered) - """ - account_info = {} - - account_info['server'] = self._owner.get_server() - - khash = psutils.pubkey_to_keyid(self._owner.props.key) - account_info['account'] = "%s@%s" % (khash, account_info['server']) - - account_info['password'] = self._owner.get_key_hash() - account_info['register'] = not self._owner.get_registered() - - print "ACCT: %s" % account_info - return account_info - - def _find_existing_connection(self): - """Try to find an existing Telepathy connection to this server - - filters the set of connections from - telepathy.client.Connection.get_connections - to find a connection using our protocol with the - "self handle" of that connection being a handle - which matches our account (see _get_account_info) - - returns connection or None - """ - our_name = self._account['account'] - - # Search existing connections, if any, that we might be able to use - connections = Connection.get_connections() - conn = None - for item in connections: - if not item.object_path.startswith(_OBJ_PATH_PREFIX): - continue - if item[CONN_INTERFACE].GetProtocol() != _PROTOCOL: - continue - if item[CONN_INTERFACE].GetStatus() == CONNECTION_STATUS_CONNECTED: - test_handle = item[CONN_INTERFACE].RequestHandles( - HANDLE_TYPE_CONTACT, [our_name])[0] - if item[CONN_INTERFACE].GetSelfHandle() != test_handle: - continue - return item - return None - - def get_connection(self): - """Retrieve our telepathy.client.Connection object""" - return self._conn - - def _init_connection(self): - """Set up our connection - - if there is no existing connection - (_find_existing_connection returns None) - produce a new connection with our protocol for our - account. - - if there is an existing connection, reuse it by - registering for various of events on it. - """ - conn = self._find_existing_connection() - if not conn: - acct = self._account.copy() - - # 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 - - conn[CONN_INTERFACE].connect_to_signal('StatusChanged', - self._status_changed_cb) - conn[CONN_INTERFACE].connect_to_signal('NewChannel', - self._new_channel_cb) - - # hack - conn._valid_interfaces.add(CONN_INTERFACE_PRESENCE) - conn._valid_interfaces.add(CONN_INTERFACE_BUDDY_INFO) - conn._valid_interfaces.add(CONN_INTERFACE_ACTIVITY_PROPERTIES) - conn._valid_interfaces.add(CONN_INTERFACE_AVATARS) - conn._valid_interfaces.add(CONN_INTERFACE_ALIASING) - - conn[CONN_INTERFACE_PRESENCE].connect_to_signal('PresenceUpdate', - self._presence_update_cb) - - self._conn = conn - status = self._conn[CONN_INTERFACE].GetStatus() - - if status == CONNECTION_STATUS_DISCONNECTED: - def connect_reply(): - _logger.debug('Connect() succeeded') - def connect_error(e): - _logger.debug('Connect() failed: %s', e) - if not self._reconnect_id: - self._reconnect_id = gobject.timeout_add(_RECONNECT_TIMEOUT, - self._reconnect_cb) - - self._conn[CONN_INTERFACE].Connect(reply_handler=connect_reply, - error_handler=connect_error) - - self._handle_connection_status_change(status, - CONNECTION_STATUS_REASON_NONE_SPECIFIED) - - 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) - - # request both handles at the same time to reduce round-trips - pub_handle, sub_handle = self._conn[CONN_INTERFACE].RequestHandles( - HANDLE_TYPE_LIST, ['publish', 'subscribe']) - - # the group of contacts who may receive your presence - publish = self._conn.request_channel(CHANNEL_TYPE_CONTACT_LIST, - HANDLE_TYPE_LIST, pub_handle, True) - self._publish_channel = publish - publish[CHANNEL_INTERFACE_GROUP].connect_to_signal('MembersChanged', - self._publish_members_changed_cb) - publish_handles, local_pending, remote_pending = \ - publish[CHANNEL_INTERFACE_GROUP].GetAllMembers() - - # the group of contacts for whom you wish to receive presence - subscribe = self._conn.request_channel(CHANNEL_TYPE_CONTACT_LIST, - HANDLE_TYPE_LIST, sub_handle, True) - self._subscribe_channel = subscribe - subscribe[CHANNEL_INTERFACE_GROUP].connect_to_signal('MembersChanged', - self._subscribe_members_changed_cb) - subscribe_handles, subscribe_lp, subscribe_rp = \ - subscribe[CHANNEL_INTERFACE_GROUP].GetAllMembers() - self._subscribe_members = set(subscribe_handles) - self._subscribe_local_pending = set(subscribe_lp) - self._subscribe_remote_pending = set(subscribe_rp) - - if local_pending: - # accept pending subscriptions - publish[CHANNEL_INTERFACE_GROUP].AddMembers(local_pending, '') - - self.self_handle = self._conn[CONN_INTERFACE].GetSelfHandle() - self._online_contacts[self.self_handle] = self._account['account'] - - # request subscriptions from people subscribed to us if we're not - # subscribed to them - not_subscribed = list(set(publish_handles) - set(subscribe_handles)) - subscribe[CHANNEL_INTERFACE_GROUP].AddMembers(not_subscribed, '') - - if CONN_INTERFACE_BUDDY_INFO not in self._conn.get_valid_interfaces(): - _logger.debug('OLPC information not available') - return False - - self._conn[CONN_INTERFACE_BUDDY_INFO].connect_to_signal( - 'PropertiesChanged', self._buddy_properties_changed_cb) - self._conn[CONN_INTERFACE_BUDDY_INFO].connect_to_signal( - 'ActivitiesChanged', self._buddy_activities_changed_cb) - self._conn[CONN_INTERFACE_BUDDY_INFO].connect_to_signal( - 'CurrentActivityChanged', - self._buddy_current_activity_changed_cb) - - self._conn[CONN_INTERFACE_AVATARS].connect_to_signal('AvatarUpdated', - self._avatar_updated_cb) - self._conn[CONN_INTERFACE_ALIASING].connect_to_signal('AliasesChanged', - self._alias_changed_cb) - self._conn[CONN_INTERFACE_ACTIVITY_PROPERTIES].connect_to_signal( - 'ActivityPropertiesChanged', - self._activity_properties_changed_cb) - - # Set initial buddy properties, avatar, and activities - self._set_self_olpc_properties() - self._set_self_alias() - # Hack; send twice to make sure the server gets it - gobject.timeout_add(1000, self._set_self_alias) - self._set_self_activities() - self._set_self_current_activity() - self._set_self_avatar() - - # Request presence for everyone we're subscribed to - self._conn[CONN_INTERFACE_PRESENCE].RequestPresence(subscribe_handles) - return True - - def _set_self_avatar_cb(self, token): - self._icon_cache.set_avatar(hash, token) - - def _set_self_avatar(self, icon_data=None): - if not icon_data: - icon_data = self._owner.props.icon - - m = md5() - m.update(icon_data) - hash = m.hexdigest() - - self_handle = self._conn[CONN_INTERFACE].GetSelfHandle() - token = self._conn[CONN_INTERFACE_AVATARS].GetAvatarTokens( - [self_handle])[0] - - if self._icon_cache.check_avatar(hash, token): - # avatar is up to date - return - - types, minw, minh, maxw, maxh, maxsize = \ - self._conn[CONN_INTERFACE_AVATARS].GetAvatarRequirements() - if not "image/jpeg" in types: - _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) - self._conn[CONN_INTERFACE_AVATARS].SetAvatar(img_data, "image/jpeg", - reply_handler=self._set_self_avatar_cb, - error_handler=lambda e: self._log_error_cb("setting avatar", e)) - - def _join_activity_channel_props_set_cb(self, activity_id, signal, handle, - channel, userdata): - self._joined_activities.append((activity_id, handle)) - self._set_self_activities() - self.emit(signal, activity_id, channel, None, userdata) - - def _join_activity_channel_props_listed_cb(self, activity_id, signal, - handle, channel, userdata, - props, prop_specs): - - props_to_set = [] - for ident, name, sig, flags in prop_specs: - value = props.pop(name, None) - if value is not None: - if flags & PROPERTY_FLAG_WRITE: - props_to_set.append((ident, value)) - # FIXME: else error, but only if we're creating the room? - # FIXME: if props is nonempty, then we want to set props that aren't - # supported here - raise an error? - - if props_to_set: - channel[PROPERTIES_INTERFACE].SetProperties(props_to_set, - reply_handler=lambda: self._join_activity_channel_props_set_cb( - activity_id, signal, handle, channel, userdata), - error_handler=lambda e: self._join_error_cb( - activity_id, signal, userdata, - 'SetProperties(%r)' % props_to_set, e)) - else: - self._join_activity_channel_props_set_cb(activity_id, signal, - handle, channel, userdata) - - def _join_activity_create_channel_cb(self, activity_id, signal, handle, - userdata, chan_path): - channel = Channel(self._conn.service_name, chan_path) - props = { - 'anonymous': False, # otherwise buddy resolution breaks - 'invite-only': False, # XXX: should be True in future - #'name': ... # XXX: set from activity name? - 'persistent': False, # vanish when there are no members - 'private': False, # XXX: should be True unless public - } - channel[PROPERTIES_INTERFACE].ListProperties( - reply_handler=lambda prop_specs: self._join_activity_channel_props_listed_cb( - activity_id, signal, handle, channel, userdata, props, prop_specs), - error_handler=lambda e: self._join_error_cb( - activity_id, signal, userdata, 'ListProperties', e)) - - def _join_activity_get_channel_cb(self, activity_id, signal, userdata, - handles): - if not self._activities.has_key(activity_id): - self._activities[activity_id] = handles[0] - - if (activity_id, handles[0]) in self._joined_activities: - e = RuntimeError("Already joined activity %s" % activity_id) - _logger.debug('%s', e) - self.emit(signal, activity_id, None, e, userdata) - return - - self._conn[CONN_INTERFACE].RequestChannel(CHANNEL_TYPE_TEXT, - HANDLE_TYPE_ROOM, handles[0], True, - reply_handler=lambda *args: self._join_activity_create_channel_cb( - activity_id, signal, handles[0], userdata, *args), - error_handler=lambda e: self._join_error_cb(activity_id, signal, - userdata, 'RequestChannel(TEXT, ROOM, %r, True)' % handles[0], - e)) - - def _join_error_cb(self, activity_id, signal, userdata, where, err): - e = Exception("Error joining/sharing activity %s: (%s): %s" - % (activity_id, where, err)) - _logger.debug('%s', e) - self.emit(signal, activity_id, None, e, userdata) - - def _internal_join_activity(self, activity_id, signal, userdata): - handle = self._activities.get(activity_id) - if not handle: - # FIXME: figure out why the server can't figure this out itself - room_jid = activity_id + "@conference." + self._account["server"] - self._conn[CONN_INTERFACE].RequestHandles(HANDLE_TYPE_ROOM, - [room_jid], - reply_handler=lambda *args: self._join_activity_get_channel_cb( - activity_id, signal, userdata, *args), - error_handler=lambda e: self._join_error_cb(activity_id, - signal, userdata, 'RequestHandles([%u])' % room_jid, - e)) - else: - self._join_activity_get_channel_cb(activity_id, signal, userdata, - [handle]) - - def share_activity(self, activity_id, userdata): - """Share activity with the network - - activity_id -- unique ID for the activity - userdata -- opaque token to be passed in the resulting event - (id, callback, errback) normally - - Asks the Telepathy server to create a "conference" channel - for the activity or return a handle to an already created - conference channel for the activity. - """ - self._internal_join_activity(activity_id, "activity-shared", userdata) - - def join_activity(self, activity_id, userdata): - """Join an activity on the network (or locally) - - activity_id -- unique ID for the activity - userdata -- opaque token to be passed in the resulting event - (id, callback, errback) normally - - Asks the Telepathy server to create a "conference" channel - for the activity or return a handle to an already created - conference channel for the activity. - """ - self._internal_join_activity(activity_id, "activity-joined", userdata) - - def _ignore_success_cb(self): - """Ignore an event (null-operation)""" - - def _log_error_cb(self, msg, err): - """Log a message (error) at debug level with prefix msg""" - _logger.debug("Error %s: %s", msg, err) - - def _set_self_olpc_properties(self): - """Set color and key on our Telepathy server identity""" - props = {} - props['color'] = self._owner.props.color - props['key'] = dbus.ByteArray(self._owner.props.key) - addr = self._owner.props.ip4_address - if not addr: - props['ip4-address'] = "" - else: - props['ip4-address'] = addr - self._conn[CONN_INTERFACE_BUDDY_INFO].SetProperties(props, - reply_handler=self._ignore_success_cb, - error_handler=lambda e: self._log_error_cb("setting properties", e)) - - def _set_self_alias(self): - """Forwarded to SetActivities on AliasInfo channel""" - alias = self._owner.props.nick - self_handle = self._conn[CONN_INTERFACE].GetSelfHandle() - self._conn[CONN_INTERFACE_ALIASING].SetAliases({self_handle : alias}, - reply_handler=self._ignore_success_cb, - error_handler=lambda e: self._log_error_cb("setting alias", e)) - return False - - def _set_self_activities(self): - """Forward set of joined activities to network - - uses SetActivities on BuddyInfo channel - """ - self._conn[CONN_INTERFACE_BUDDY_INFO].SetActivities( - self._joined_activities, - reply_handler=self._ignore_success_cb, - error_handler=lambda e: self._log_error_cb("setting activities", e)) - - def _set_self_current_activity(self): - """Forward our current activity (or "") to network - - uses SetCurrentActivity on BuddyInfo channel - """ - cur_activity = self._owner.props.current_activity - cur_activity_handle = 0 - if not cur_activity: - cur_activity = "" - else: - cur_activity_handle = self._get_handle_for_activity(cur_activity) - if not cur_activity_handle: - # dont advertise a current activity that's not shared - cur_activity = "" - - _logger.debug("Setting current activity to '%s' (handle %s)", - cur_activity, cur_activity_handle) - self._conn[CONN_INTERFACE_BUDDY_INFO].SetCurrentActivity(cur_activity, - cur_activity_handle, - reply_handler=self._ignore_success_cb, - error_handler=lambda e: self._log_error_cb("setting current activity", e)) - - def _get_handle_for_activity(self, activity_id): - """Retrieve current handle for given activity or None""" - for (act, handle) in self._joined_activities: - if activity_id == act: - return handle - return None - - def _reconnect_cb(self): - """Attempt to reconnect to the server""" - self.start() - return False - - def _handle_connection_status_change(self, status, reason): - if status == self._conn_status: - return - - if status == CONNECTION_STATUS_CONNECTING: - self._conn_status = status - _logger.debug("status: connecting...") - 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") - elif status == CONNECTION_STATUS_DISCONNECTED: - self.cleanup() - _logger.debug("status: disconnected (reason %r)", reason) - if reason == CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED: - # FIXME: handle connection failure; retry later? - pass - else: - # If disconnected, but still have a network connection, retry - # If disconnected and no network connection, do nothing here - # and let the IP4AddressMonitor address-changed signal handle - # reconnection - if self._ip4am.props.address and not self._reconnect_id: - self._reconnect_id = gobject.timeout_add(_RECONNECT_TIMEOUT, - self._reconnect_cb) - - self.emit('status', self._conn_status, int(reason)) - return False - - def _status_changed_cb(self, status, reason): - """Handle notification of connection-status change - - status -- CONNECTION_STATUS_* - reason -- integer code describing the reason... - """ - _logger.debug("::: connection status changed to %s", status) - self._handle_connection_status_change(status, reason) - - def start(self): - """Start up the Telepathy networking connections - - if we are already connected, query for the initial contact - information. - - if we are already connecting, do nothing - - otherwise initiate a connection and transfer control to - _connect_reply_cb or _connect_error_cb - """ - _logger.debug("Starting up...") - - if self._reconnect_id > 0: - gobject.source_remove(self._reconnect_id) - self._reconnect_id = 0 - - # Only init connection if we have a valid IP address - if self._ip4am.props.address: - _logger.debug("::: Have IP4 address %s, will connect", - self._ip4am.props.address) - self._init_connection() - else: - _logger.debug("::: No IP4 address, postponing connection") - - def cleanup(self): - """If we still have a connection, disconnect it""" - if self._conn: - try: - self._conn[CONN_INTERFACE].Disconnect() - except: - pass - self._conn = None - self._conn_status = CONNECTION_STATUS_DISCONNECTED - - for handle in self._online_contacts.keys(): - self._contact_offline(handle) - self._online_contacts = {} - self._joined_activites = [] - self._activites = {} - - if self._reconnect_id > 0: - gobject.source_remove(self._reconnect_id) - self._reconnect_id = 0 - - def _contact_offline(self, handle): - """Handle contact going offline (send message, update set)""" - if not self._online_contacts.has_key(handle): - return - if self._online_contacts[handle]: - self.emit("contact-offline", handle) - del self._online_contacts[handle] - - def _contact_online_activities_cb(self, handle, activities): - """Handle contact's activity list update""" - self._buddy_activities_changed_cb(handle, activities) - - def _contact_online_activities_error_cb(self, handle, err): - """Handle contact's activity list being unavailable""" - _logger.debug("Handle %s - Error getting activities: %s", - handle, err) - # Don't drop the buddy if we can't get their activities, for now - #self._contact_offline(handle) - - def _contact_online_aliases_cb(self, handle, props, aliases): - """Handle contact's alias being received (do further queries)""" - if not self._conn or not aliases or not len(aliases): - _logger.debug("Handle %s - No aliases", handle) - self._contact_offline(handle) - return - - props['nick'] = aliases[0] - - jid = self._conn[CONN_INTERFACE].InspectHandles(HANDLE_TYPE_CONTACT, - [handle])[0] - self._online_contacts[handle] = jid - objid = self.identify_contacts(None, [handle])[handle] - - self.emit("contact-online", objid, handle, props) - - self._conn[CONN_INTERFACE_BUDDY_INFO].GetActivities(handle, - reply_handler=lambda *args: self._contact_online_activities_cb( - handle, *args), - error_handler=lambda e: self._contact_online_activities_error_cb( - handle, e)) - - def _contact_online_aliases_error_cb(self, handle, props, retry, err): - """Handle failure to retrieve given user's alias/information""" - if retry: - _logger.debug("Handle %s - Error getting nickname (will retry):" - "%s", handle, err) - self._conn[CONN_INTERFACE_ALIASING].RequestAliases([handle], - reply_handler=lambda *args: self._contact_online_aliases_cb( - handle, props, *args), - error_handler=lambda e: self._contact_online_aliases_error_cb( - handle, props, False, e)) - else: - _logger.debug("Handle %s - Error getting nickname: %s", - handle, err) - self._contact_offline(handle) - - def _contact_online_properties_cb(self, handle, props): - """Handle failure to retrieve given user's alias/information""" - if not props.has_key('key'): - _logger.debug("Handle %s - invalid key.", handle) - self._contact_offline(handle) - return - if not props.has_key('color'): - _logger.debug("Handle %s - invalid color.", handle) - self._contact_offline(handle) - return - - self._conn[CONN_INTERFACE_ALIASING].RequestAliases([handle], - reply_handler=lambda *args: self._contact_online_aliases_cb( - handle, props, *args), - error_handler=lambda e: self._contact_online_aliases_error_cb( - handle, props, True, e)) - - def _contact_online_request_properties(self, handle, tries): - self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(handle, - byte_arrays=True, - reply_handler=lambda *args: self._contact_online_properties_cb( - handle, *args), - error_handler=lambda e: self._contact_online_properties_error_cb( - handle, tries, e)) - return False - - def _contact_online_properties_error_cb(self, handle, tries, err): - """Handle error retrieving property-set for a user (handle)""" - if tries <= 3: - _logger.debug("Handle %s - Error getting properties (will retry):" - " %s", handle, err) - tries += 1 - gobject.timeout_add(1000, self._contact_online_request_properties, - handle, tries) - else: - _logger.debug("Handle %s - Error getting properties: %s", - handle, err) - self._contact_offline(handle) - - def _contact_online(self, handle): - """Handle a contact coming online""" - if (handle not in self._subscribe_members and - handle not in self._subscribe_local_pending and - handle not in self._subscribe_remote_pending): - # it's probably a channel-specific handle - can't create a Buddy - # object for those yet - return - - self._online_contacts[handle] = None - if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): - jid = self._conn[CONN_INTERFACE].InspectHandles( - HANDLE_TYPE_CONTACT, [handle])[0] - self._online_contacts[handle] = jid - # ignore network events for Owner property changes since those - # are handled locally - return - - self._contact_online_request_properties(handle, 1) - - def _subscribe_members_changed_cb(self, message, added, removed, - local_pending, remote_pending, - actor, reason): - - added = set(added) - removed = set(removed) - local_pending = set(local_pending) - remote_pending = set(remote_pending) - - affected = added|removed - affected |= local_pending - affected |= remote_pending - - self._subscribe_members -= affected - self._subscribe_members |= added - self._subscribe_local_pending -= affected - self._subscribe_local_pending |= local_pending - 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): - - if local_pending: - # accept all requested subscriptions - self._publish_channel[CHANNEL_INTERFACE_GROUP].AddMembers( - local_pending, '') - - # subscribe to people who've subscribed to us, if necessary - added = list(set(added) - self._subscribe_members - - self._subscribe_remote_pending) - if added: - self._subscribe_channel[CHANNEL_INTERFACE_GROUP].AddMembers( - added, '') - - def _presence_update_cb(self, presence): - """Send update for online/offline status of presence""" - for handle in presence: - timestamp, statuses = presence[handle] - online = handle in self._online_contacts - for status, params in statuses.items(): - if not online and status == "offline": - # weren't online in the first place... - continue - jid = self._conn[CONN_INTERFACE].InspectHandles( - HANDLE_TYPE_CONTACT, [handle])[0] - olstr = "ONLINE" - if not online: olstr = "OFFLINE" - _logger.debug("Handle %s (%s) was %s, status now '%s'.", - handle, jid, olstr, status) - if not online and status in ["available", "away", "brb", - "busy", "dnd", "xa"]: - self._contact_online(handle) - elif status in ["offline", "invisible"]: - self._contact_offline(handle) - - def _request_avatar_cb(self, handle, new_avatar_token, avatar, mime_type): - jid = self._online_contacts[handle] - if not jid: - logging.debug("Handle %s not valid yet..." % handle) - return - icon = ''.join(map(chr, avatar)) - self._icon_cache.store_icon(jid, new_avatar_token, icon) - self.emit("avatar-updated", handle, icon) - - def _avatar_updated_cb(self, handle, new_avatar_token): - """Handle update of given user (handle)'s avatar""" - if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): - # ignore network events for Owner property changes since those - # are handled locally - return - - if not self._online_contacts.has_key(handle): - _logger.debug("Handle %s unknown.", handle) - return - - jid = self._online_contacts[handle] - if not jid: - _logger.debug("Handle %s not valid yet...", handle) - return - - icon = self._icon_cache.get_icon(jid, new_avatar_token) - if not icon: - # cache miss - self._conn[CONN_INTERFACE_AVATARS].RequestAvatar(handle, - reply_handler=lambda *args: self._request_avatar_cb(handle, - new_avatar_token, *args), - error_handler=lambda e: self._log_error_cb( - "getting avatar", e)) - else: - self.emit("avatar-updated", handle, icon) - - def _alias_changed_cb(self, aliases): - """Handle update of aliases for all users""" - for handle, alias in aliases: - prop = {'nick': alias} - #print "Buddy %s alias changed to %s" % (handle, alias) - if (self._online_contacts.has_key(handle) and - self._online_contacts[handle]): - self._buddy_properties_changed_cb(handle, prop) - - def _buddy_properties_changed_cb(self, handle, properties): - """Handle update of given user (handle)'s properties""" - if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): - # ignore network events for Owner property changes since those - # are handled locally - return - if (self._online_contacts.has_key(handle) and - self._online_contacts[handle]): - self.emit("buddy-properties-changed", handle, properties) - - def _buddy_activities_changed_cb(self, handle, activities): - """Handle update of given user (handle)'s activities""" - if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): - # ignore network events for Owner activity changes since those - # are handled locally - return - if (not self._online_contacts.has_key(handle) or - not self._online_contacts[handle]): - return - - for act_id, act_handle in activities: - self._activities[act_id] = act_handle - activities_id = map(lambda x: x[0], activities) - self.emit("buddy-activities-changed", handle, activities_id) - - def _buddy_current_activity_changed_cb(self, handle, activity, channel): - """Handle update of given user (handle)'s current activity""" - - if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): - # ignore network events for Owner current activity changes since - # those are handled locally - return - if (not self._online_contacts.has_key(handle) or - not self._online_contacts[handle]): - return - - if not len(activity) or not util.validate_activity_id(activity): - activity = None - prop = {'current-activity': activity} - _logger.debug("Handle %s: current activity now %s", handle, activity) - self._buddy_properties_changed_cb(handle, prop) - - def _new_channel_cb(self, object_path, channel_type, handle_type, handle, - suppress_handler): - """Handle creation of a new channel - """ - if (handle_type == HANDLE_TYPE_ROOM and - channel_type == CHANNEL_TYPE_TEXT): - def ready(channel): - - for act_id, act_handle in self._activities.iteritems(): - if handle == act_handle: - break - else: - return - - def got_all_members(current, local_pending, remote_pending): - if local_pending: - for act_id, act_handle in self._activities.iteritems(): - if handle == act_handle: - self.emit('activity-invitation', act_id) - def got_all_members_err(e): - logger.debug('Unable to get channel members for %s:', - object_path, exc_info=1) - - group = channel[CHANNEL_INTERFACE_GROUP] - group.GetAllMembers(reply_handler=got_all_members, - error_handler=got_all_members_err) - - # we throw away the channel as soon as ready() finishes - Channel(self._conn.service_name, object_path, - ready_handler=ready) - - elif (handle_type == HANDLE_TYPE_CONTACT and - channel_type in (CHANNEL_TYPE_TEXT, - CHANNEL_TYPE_STREAMED_MEDIA)): - self.emit("private-invitation", object_path) - - def update_activity_properties(self, act_id): - """Request update from network on the activity properties of act_id""" - handle = self._activities.get(act_id) - if not handle: - raise RuntimeError("Unknown activity %s: couldn't find handle.") - - self._conn[CONN_INTERFACE_ACTIVITY_PROPERTIES].GetProperties(handle, - reply_handler=lambda *args: self._activity_properties_changed_cb( - handle, *args), - error_handler=lambda e: self._log_error_cb( - "getting activity properties", e)) - - def set_activity_properties(self, act_id, props): - """Send update to network on the activity properties of act_id (props). - """ - handle = self._activities.get(act_id) - if not handle: - raise RuntimeError("Unknown activity %s: couldn't find handle.") - - self._conn[CONN_INTERFACE_ACTIVITY_PROPERTIES].SetProperties(handle, - props, reply_handler=self._ignore_success_cb, - error_handler=lambda e: self._log_error_cb( - "setting activity properties", e)) - - def _activity_properties_changed_cb(self, room, properties): - """Handle update of properties for a "room" (activity handle)""" - for act_id, act_handle in self._activities.items(): - if room == act_handle: - self.emit("activity-properties-changed", act_id, properties) - return - - 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 - register JIDs whose username part is either a public key fingerprint, - or of the wrong form to be a public key fingerprint (to allow for - ejabberd's admin@example.com address). - - If we trust the server, we can skip verifying the key ourselves, - which leads to simplifications. In the current implementation we - never verify that people actually own the key they claim to, so - we will always give contacts on untrusted servers a JID- rather than - key-based identity. - - For the moment we assume that the test server, olpc.collabora.co.uk, - does this verification. - """ - return (hostname == 'olpc.collabora.co.uk') - - def identify_contacts(self, tp_chan, handles): - """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) - - owners = handles - - if tp_chan is not None and CHANNEL_INTERFACE_GROUP in tp_chan: - group = tp_chan[CHANNEL_INTERFACE_GROUP] - if (group.GetGroupFlags() & - CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES): - owners = group.GetHandleOwners(handles) - for i, owner in enumerate(owners): - if owner == 0: - owners[i] = handles[i] - else: - group = None - - jids = self._conn[CONN_INTERFACE].InspectHandles(HANDLE_TYPE_CONTACT, - owners) - - ret = {} - for handle, jid in zip(handles, jids): - # special-case the Owner - we always know who we are - if (handle == self.self_handle or - (group is not None and handle == group.GetSelfHandle())): - ret[handle] = self._owner.props.objid - continue - - if '/' in jid: - # the contact is unidentifiable (in an anonymous MUC) - create - # a temporary identity for them, based on their room-JID - ret[handle] = 'xmpp/' + psutils.escape_identifier(jid) - else: - user, host = jid.split('@', 1) - if (self._server_is_trusted(host) and len(user) == 40 and - user.strip(hexdigits) == ''): - # they're on a trusted server and their username looks - # like a key-ID - ret[handle] = 'keyid/' + user.lower() - else: - # untrusted server, or not the right format to be a - # key-ID - identify the contact by their JID - ret[handle] = 'xmpp/' + psutils.escape_identifier(jid) - - return ret |