Replace enough of the old PS so we can share an activity instance publically

on the network.
This commit is contained in:
Tomeu Vizoso 2010-06-28 16:42:23 +02:00
parent 98cc77f1fb
commit a0b9394846
3 changed files with 165 additions and 77 deletions

View File

@ -268,7 +268,6 @@ class Activity(Window, gtk.Container):
self._active = False self._active = False
self._activity_id = handle.activity_id self._activity_id = handle.activity_id
self.shared_activity = None self.shared_activity = None
self._share_id = None
self._join_id = None self._join_id = None
self._updating_jobject = False self._updating_jobject = False
self._closing = False self._closing = False
@ -639,6 +638,7 @@ class Activity(Window, gtk.Container):
self._jobject.object_id = None self._jobject.object_id = None
def __privacy_changed_cb(self, shared_activity, param_spec): def __privacy_changed_cb(self, shared_activity, param_spec):
logging.debug('__privacy_changed_cb %r', shared_activity.props.private)
if shared_activity.props.private: if shared_activity.props.private:
self._jobject.metadata['share-scope'] = SCOPE_INVITE_ONLY self._jobject.metadata['share-scope'] = SCOPE_INVITE_ONLY
else: else:
@ -670,8 +670,6 @@ class Activity(Window, gtk.Container):
return self.shared_activity.props.joined return self.shared_activity.props.joined
def __share_cb(self, ps, success, activity, err): def __share_cb(self, ps, success, activity, err):
self._pservice.disconnect(self._share_id)
self._share_id = None
if not success: if not success:
logging.debug('Share of activity %s failed: %s.', logging.debug('Share of activity %s failed: %s.',
self._activity_id, err) self._activity_id, err)
@ -734,9 +732,9 @@ class Activity(Window, gtk.Container):
verb = private and 'private' or 'public' verb = private and 'private' or 'public'
logging.debug('Requesting %s share of activity %s.', verb, logging.debug('Requesting %s share of activity %s.', verb,
self._activity_id) self._activity_id)
self._share_id = self._pservice.connect("activity-shared", pservice = presenceservice.get_instance()
self.__share_cb) pservice.connect('activity-shared', self.__share_cb)
self._pservice.share_activity(self, private=private) pservice.share_activity(self, private=private)
def _show_keep_failed_dialog(self): def _show_keep_failed_dialog(self):
alert = Alert() alert = Alert()

View File

@ -21,6 +21,7 @@ STABLE.
""" """
import logging import logging
from functools import partial
import dbus import dbus
import gobject import gobject
@ -33,6 +34,7 @@ from telepathy.interfaces import CHANNEL, \
from telepathy.constants import HANDLE_TYPE_ROOM from telepathy.constants import HANDLE_TYPE_ROOM
CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties' CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
_logger = logging.getLogger('sugar.presence.activity') _logger = logging.getLogger('sugar.presence.activity')
@ -71,7 +73,13 @@ class Activity(gobject.GObject):
'joined': (bool, None, None, False, gobject.PARAM_READABLE), 'joined': (bool, None, None, False, gobject.PARAM_READABLE),
} }
def __init__(self, connection, room_handle): def __init__(self, connection, room_handle=None, properties=None):
if room_handle is None and properties is None:
raise ValueError('Need to pass one of room_handle or properties')
if properties is None:
properties = {}
gobject.GObject.__init__(self) gobject.GObject.__init__(self)
self.telepathy_conn = connection self.telepathy_conn = connection
@ -79,18 +87,23 @@ class Activity(gobject.GObject):
self.telepathy_tubes_chan = None self.telepathy_tubes_chan = None
self._room_handle = room_handle self._room_handle = room_handle
self._id = None self._id = properties.get('id', None)
self._color = None self._color = properties.get('color', None)
self._name = None self._name = properties.get('name', None)
self._type = None self._type = properties.get('type', None)
self._tags = None self._tags = properties.get('tags', None)
self._private = True self._private = properties.get('private', True)
self._joined = False self._joined = properties.get('joined', False)
self._get_properties_call = None
if not self._room_handle is None:
self._start_tracking_properties()
def _start_tracking_properties(self):
bus = dbus.SessionBus() bus = dbus.SessionBus()
self._get_properties_call = bus.call_async( self._get_properties_call = bus.call_async(
connection.requested_bus_name, self.telepathy_conn.requested_bus_name,
connection.object_path, self.telepathy_conn.object_path,
CONN_INTERFACE_ACTIVITY_PROPERTIES, CONN_INTERFACE_ACTIVITY_PROPERTIES,
'GetProperties', 'GetProperties',
'u', 'u',
@ -99,17 +112,24 @@ class Activity(gobject.GObject):
error_handler=self._error_handler_cb, error_handler=self._error_handler_cb,
utf8_strings=True) utf8_strings=True)
# As only one Activity instance is needed per activity process,
# we can afford listening to ActivityPropertiesChanged like this.
self.telepathy_conn.connect_to_signal(
'ActivityPropertiesChanged',
self.__activity_properties_changed_cb,
dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
def __activity_properties_changed_cb(self, room_handle, properties):
_logger.debug('%r: Activity properties changed to %r', self, properties)
self._update_properties(properties)
def _got_properties_cb(self, properties): def _got_properties_cb(self, properties):
_logger.debug('_got_properties_cb', properties) _logger.debug('_got_properties_cb %r', properties)
self._get_properties_call = None self._get_properties_call = None
self._update_properties(properties) self._update_properties(properties)
def _error_handler_cb(self, error): def _error_handler_cb(self, error):
_logger.debug('_error_handler_cb', error) _logger.debug('_error_handler_cb %r', error)
def _properties_changed_cb(self, new_props):
_logger.debug('%r: Activity properties changed to %r', self, new_props)
self._update_properties(new_props)
def _update_properties(self, new_props): def _update_properties(self, new_props):
val = new_props.get('name', self._name) val = new_props.get('name', self._name)
@ -169,20 +189,22 @@ class Activity(gobject.GObject):
"""Set a particular property in our property dictionary""" """Set a particular property in our property dictionary"""
# FIXME: need an asynchronous API to set these properties, # FIXME: need an asynchronous API to set these properties,
# particularly 'private' # particularly 'private'
if pspec.name == "name": if pspec.name == "name":
self._activity.SetProperties({'name': val})
self._name = val self._name = val
elif pspec.name == "color": elif pspec.name == "color":
self._activity.SetProperties({'color': val})
self._color = val self._color = val
elif pspec.name == "tags": elif pspec.name == "tags":
self._activity.SetProperties({'tags': val})
self._tags = val self._tags = val
elif pspec.name == "private": elif pspec.name == "private":
self._activity.SetProperties({'private': val})
self._private = val self._private = val
else:
raise ValueError('Unknown property "%s"', pspec.name)
self._publish_properties()
def set_private(self, val, reply_handler, error_handler): def set_private(self, val, reply_handler, error_handler):
_logger.debug('set_private %r', val)
self._activity.SetProperties({'private': bool(val)}, self._activity.SetProperties({'private': bool(val)},
reply_handler=reply_handler, reply_handler=reply_handler,
error_handler=error_handler) error_handler=error_handler)
@ -263,6 +285,9 @@ class Activity(gobject.GObject):
def set_up_tubes(self, reply_handler, error_handler): def set_up_tubes(self, reply_handler, error_handler):
if self._room_handle is None:
raise ValueError("Don't have a handle for the room yet")
chans = [] chans = []
def tubes_ready(): def tubes_ready():
@ -329,6 +354,70 @@ class Activity(gobject.GObject):
self.set_up_tubes(reply_handler=self._join_cb, self.set_up_tubes(reply_handler=self._join_cb,
error_handler=self._join_error_cb) error_handler=self._join_error_cb)
def share(self, share_activity_cb, share_activity_error_cb):
if not self._room_handle is None:
raise ValueError('Already have a room handle')
""" TODO: Check we don't need this
# We shouldn't have to do this, but Gabble sometimes finds the IRC
# transport and goes "that has chatrooms, that'll do nicely". Work
# around it til Gabble gets better at finding the MUC service.
return '%s@%s' % (activity_id,
self._account['fallback-conference-server'])
"""
self.telepathy_conn.RequestHandles(
HANDLE_TYPE_ROOM,
[self._id],
reply_handler=partial(self.__got_handles_cb, share_activity_cb, share_activity_error_cb),
error_handler=partial(self.__share_error_cb, share_activity_error_cb),
dbus_interface=CONNECTION)
def __got_handles_cb(self, share_activity_cb, share_activity_error_cb, handles):
logging.debug('__got_handles_cb %r', handles)
self._room_handle = handles[0]
self._joined = True
self.set_up_tubes(
partial(self.__tubes_set_up_cb, share_activity_cb, share_activity_error_cb),
share_activity_error_cb)
def __tubes_set_up_cb(self, share_activity_cb, share_activity_error_cb):
self.telepathy_conn.AddActivity(
self._id,
self._room_handle,
reply_handler=partial(self.__added_activity_cb, share_activity_cb),
error_handler=partial(self.__share_error_cb, share_activity_error_cb),
dbus_interface=CONN_INTERFACE_BUDDY_INFO)
def __added_activity_cb(self, share_activity_cb):
self._publish_properties()
self._start_tracking_properties()
share_activity_cb(self)
def _publish_properties(self):
properties = {}
if self._color is not None:
properties['color'] = self._color
if self._name is not None:
properties['name'] = self._name
if self._type is not None:
properties['type'] = self._type
if self._tags is not None:
properties['tags'] = self._tags
properties['private'] = self._private
logging.debug('_publish_properties calling SetProperties')
self.telepathy_conn.SetProperties(
self._room_handle,
properties,
dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
def __share_error_cb(self, share_activity_error_cb, error):
logging.debug('%r: Share failed because: %s', self, error)
share_activity_error_cb(self, error)
# GetChannels() wrapper # GetChannels() wrapper
def get_channels(self): def get_channels(self):

View File

@ -68,6 +68,7 @@ class PresenceService(gobject.GObject):
""" """
gobject.GObject.__init__(self) gobject.GObject.__init__(self)
self._activity_cache = None
self._buddy_cache = {} self._buddy_cache = {}
def _new_object(self, object_path): def _new_object(self, object_path):
@ -249,10 +250,18 @@ class PresenceService(gobject.GObject):
returns single Activity object or None if the activity returns single Activity object or None if the activity
is not found using GetActivityById on the service 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:
for connection in get_connection_manager().connections: for connection in get_connection_manager().connections:
try: try:
room_handle = connection.GetActivity(activity_id) room_handle = connection.GetActivity(activity_id)
return Activity(connection, room_handle) activity = Activity(connection, room_handle)
self._activity_cache = activity
return activity
except: except:
pass pass
@ -351,64 +360,56 @@ class PresenceService(gobject.GObject):
"""Retrieves the laptop Buddy object.""" """Retrieves the laptop Buddy object."""
return Owner() return Owner()
def _share_activity_cb(self, activity, op): def __share_activity_cb(self, activity):
"""Finish sharing the activity """Finish sharing the activity
""" """
# FIXME find a better way to shutup pylint self.emit("activity-shared", True, activity, None)
psact = self._new_object(op)
psact._joined = True
_logger.debug('%r: Just shared, setting up tubes', activity)
psact.set_up_tubes(reply_handler=lambda:
self.emit("activity-shared", True, psact, None),
error_handler=lambda e:
self._share_activity_error_cb(activity, e))
def _share_activity_error_cb(self, activity, err): def __share_activity_error_cb(self, activity, error):
"""Notify with GObject event of unsuccessful sharing of activity""" """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, activity, error)
self.emit("activity-shared", False, None, err)
def share_activity(self, activity, properties=None, private=True): def share_activity(self, activity, properties=None, 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()
if properties is None: if properties is None:
properties = {} properties = {}
# Ensure the activity is not already shared/joined if 'id' not in properties:
for obj in self._objcache.values(): properties['id'] = activity.get_id()
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() if 'type' not in properties:
name = activity.props.title properties['type'] = activity.get_bundle_id()
properties['private'] = bool(private)
self._ps.ShareActivity(actid, atype, name, properties, if 'name' not in properties:
reply_handler=lambda op: \ properties['name'] = activity.metadata.get('title', None)
self._share_activity_cb(activity, op),
error_handler=lambda e: \ if 'color' not in properties:
self._share_activity_error_cb(activity, e)) 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 = get_connection_manager().get_preferred_connection()
shared_activity = Activity(connection, properties=properties)
self._activity_cache = shared_activity
"""
if shared_activity.props.joined:
raise RuntimeError('Activity %s is already shared.' %
activity.get_id())
"""
shared_activity.share(self.__share_activity_cb,
self.__share_activity_error_cb)
def get_preferred_connection(self): def get_preferred_connection(self):
"""Gets the preferred telepathy connection object that an activity """Gets the preferred telepathy connection object that an activity
should use when talking directly to telepathy should use when talking directly to telepathy
returns the bus name and the object path of the Telepathy connection""" returns the bus name and the object path of the Telepathy connection
"""
connection = get_connection_manager().get_preferred_connection() connection = get_connection_manager().get_preferred_connection()
if connection is None: if connection is None:
return None return None