# Copyright (C) 2007, Red Hat, Inc. # Copyright (C) 2010 Collabora Ltd. # # 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. """ STABLE. """ import logging import gobject import dbus import dbus.exceptions import dbus.glib from dbus import PROPERTIES_IFACE from sugar.presence.buddy import Buddy, Owner from sugar.presence.activity import Activity from sugar.presence.connectionmanager import get_connection_manager from telepathy.interfaces import ACCOUNT, \ ACCOUNT_MANAGER, \ CONNECTION from telepathy.constants import HANDLE_TYPE_CONTACT _logger = logging.getLogger('sugar.presence.presenceservice') ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager' ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager' CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties' class PresenceService(gobject.GObject): """Provides simplified access to the Telepathy framework to activities""" __gsignals__ = { 'activity-shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), } def __init__(self): """Initialise the service and attempt to connect to events """ gobject.GObject.__init__(self) self._activity_cache = None self._buddy_cache = {} def get_activity(self, activity_id, warn_if_none=True): """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 """ if self._activity_cache is not None: if self._activity_cache.props.id != activity_id: raise RuntimeError('Activities can only access their own' ' shared instance') return self._activity_cache else: connection_manager = get_connection_manager() connections_per_account = \ connection_manager.get_connections_per_account() for account_path, connection in connections_per_account.items(): if not connection.connected: continue logging.debug('Calling GetActivity on %s', account_path) try: room_handle = connection.connection.GetActivity( activity_id, dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES) except dbus.exceptions.DBusException, e: name = 'org.freedesktop.Telepathy.Error.NotAvailable' if e.get_dbus_name() == name: logging.debug("There's no shared activity with the id " "%s", activity_id) else: raise else: activity = Activity(account_path, connection.connection, room_handle=room_handle) self._activity_cache = activity return activity return None def get_activity_by_handle(self, connection_path, room_handle): if self._activity_cache is not None: if self._activity_cache.room_handle != room_handle: raise RuntimeError('Activities can only access their own' ' shared instance') return self._activity_cache else: connection_manager = get_connection_manager() account_path = \ connection_manager.get_account_for_connection(connection_path) connection_name = connection_path.replace('/', '.')[1:] bus = dbus.SessionBus() connection = bus.get_object(connection_name, connection_path) activity = Activity(account_path, connection, room_handle=room_handle) self._activity_cache = activity return activity def get_buddy(self, account_path, contact_id): if (account_path, contact_id) in self._buddy_cache: return self._buddy_cache[(account_path, contact_id)] buddy = Buddy(account_path, contact_id) self._buddy_cache[(account_path, contact_id)] = buddy return buddy # DEPRECATED 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 """ bus = dbus.Bus() obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH) account_manager = dbus.Interface(obj, ACCOUNT_MANAGER) account_paths = account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts', dbus_interface=PROPERTIES_IFACE) for account_path in account_paths: obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, account_path) connection_path = obj.Get(ACCOUNT, 'Connection') if connection_path == tp_conn_path: connection_name = connection_path.replace('/', '.')[1:] connection = bus.get_object(connection_name, connection_path) contact_ids = connection.InspectHandles(HANDLE_TYPE_CONTACT, [handle], dbus_interface=CONNECTION) return self.get_buddy(account_path, contact_ids[0]) raise ValueError('Unknown buddy in connection %s with handle %d', tp_conn_path, handle) def get_owner(self): """Retrieves the laptop Buddy object.""" return Owner() def __share_activity_cb(self, activity): """Finish sharing the activity """ self.emit('activity-shared', True, activity, None) def __share_activity_error_cb(self, activity, error): """Notify with GObject event of unsuccessful sharing of activity """ self.emit('activity-shared', False, activity, error) def share_activity(self, activity, properties=None, private=True): if properties is None: properties = {} if 'id' not in properties: properties['id'] = activity.get_id() if 'type' not in properties: properties['type'] = activity.get_bundle_id() if 'name' not in properties: properties['name'] = activity.metadata.get('title', None) if 'color' not in properties: properties['color'] = activity.metadata.get('icon-color', None) properties['private'] = private if self._activity_cache is not None: raise ValueError('Activity %s is already tracked', activity.get_id()) connection_manager = get_connection_manager() account_path, connection = \ connection_manager.get_preferred_connection() if connection is None: self.emit('activity-shared', False, None, 'No active connection available') return shared_activity = Activity(account_path, connection, properties=properties) self._activity_cache = shared_activity if shared_activity.props.joined: raise RuntimeError('Activity %s is already shared.' % activity.props.id) shared_activity.share(self.__share_activity_cb, self.__share_activity_error_cb) 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 """ manager = get_connection_manager() account_path, connection = manager.get_preferred_connection() if connection is None: return None else: return connection.requested_bus_name, connection.object_path # DEPRECATED def get(self, object_path): raise NotImplementedError() # DEPRECATED def get_activities(self): raise NotImplementedError() # DEPRECATED def get_activities_async(self, reply_handler=None, error_handler=None): raise NotImplementedError() # DEPRECATED def get_buddies(self): raise NotImplementedError() # DEPRECATED def get_buddies_async(self, reply_handler=None, error_handler=None): raise NotImplementedError() _ps = None def get_instance(allow_offline_iface=False): """Retrieve this process' view of the PresenceService""" global _ps if not _ps: _ps = PresenceService() return _ps