Exemple #1
0
 def _new_tube_cb(self, id, initiator, type, service, params, state):
     logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
                  'params=%r state=%d', id, initiator, type, service,
                  params, state)
     if (type == telepathy.TUBE_TYPE_DBUS and
         service == SERVICE):
         if state == telepathy.TUBE_STATE_LOCAL_PENDING:
             self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
         self.tube = SugarTubeConnection(self.conn,
             self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
             id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
         super(CollaborationWrapper, self).__init__(self.tube, PATH)
         self.tube.watch_participants(self.participant_change_cb)
Exemple #2
0
    def _new_tube_cb(self, id, initiator, type, service, params, state):
        _logger.debug(
            'New tube: ID=%d initator=%d type=%d service=%s '
            'params=%r state=%d', id, initiator, type, service, params, state)

        if (type == telepathy.TUBE_TYPE_DBUS and service == IFACE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(
                    id)

            self._tube_conn = SugarTubeConnection(
                self._connection,
                self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
                id,
                group_iface=self._text_chan[telepathy.CHANNEL_INTERFACE_GROUP])

            self._tube_conn.add_signal_receiver(self._send_message_cb,
                                                'SendMessage',
                                                sender_keyword='sender')

            self._dbus_object = ShareableObject(self._tube_conn,
                                                self._service_path)
Exemple #3
0
    def _new_tube_cb(self, id, initiator, type, service, params, state):
        logging.debug('New tube: ID=%d initator=%d type=%d service=%s '
                     'params=%r state=%d', id, initiator, type, service,
                     params, state)

        if (type == TelepathyGLib.TubeType.DBUS and
                service == self.service):
            if state == TelepathyGLib.TubeState.LOCAL_PENDING:
                self._tubes_chan[TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES] \
                        .AcceptDBusTube(id)

            tube_conn = SugarTubeConnection(self._conn,
                self._tubes_chan[TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES], id,
                group_iface=self._text_chan[TelepathyGLib.IFACE_CHANNEL_INTERFACE_GROUP])

            self._share(tube_conn, self.__initiator)
    def _new_tube_cb(self, id, initiator, type, service, params, state):
        _logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
                      'params=%r state=%d', id, initiator, type, service,
                      params, state)

        if (type == telepathy.TUBE_TYPE_DBUS and service == IFACE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self._tubes_chan[
                    telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self._tube_conn = SugarTubeConnection(
                self._connection, self._tubes_chan[
                    telepathy.CHANNEL_TYPE_TUBES],
                id, group_iface=self._text_chan[
                    telepathy.CHANNEL_INTERFACE_GROUP])

            self._tube_conn.add_signal_receiver(
                self._send_message_cb,
                'SendMessage', sender_keyword='sender')

            self._dbus_object = ShareableObject(self._tube_conn,
                                                self._service_path)
class CollaborationWrapper(ExportedGObject):

    ''' A wrapper for the collaboration bureaucracy'''

    def __init__(self, activity, buddy_joined_cb,
                 buddy_left_cb, play_cb, undostack, bootstrap):
        self.activity = activity
        self.buddy_joined = buddy_joined_cb
        self.buddy_left = buddy_left_cb
        self.Play_cb = play_cb
        self.undostack = undostack
        self.bootstrap = bootstrap
        self.world = False
        self.entered = False
        self.presence_service = presenceservice.get_instance()
        self.owner = self.presence_service.get_owner()

    def _shared_cb(self, activity):
        self.activity.gameToolbar.grey_out_size_change()
        self.activity.gameToolbar.grey_out_restart()
        self.activity.gameToolbar.grey_out_ai()
        self.activity.undo_button.hide()
        self.activity.board.set_sensitive(False)
        self._sharing_setup()
        self.tubes_chan[TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].OfferDBusTube(
            SERVICE, {})
        self.is_initiator = True

    def _joined_cb(self, activity):
        self.activity.gameToolbar.grey_out_size_change()
        self.activity.gameToolbar.grey_out_restart()
        self.activity.gameToolbar.grey_out_ai()
        self.activity.undo_button.hide()
        self.activity.board.set_sensitive(False)
        self._sharing_setup()
        self.tubes_chan[TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].ListTubes(
            reply_handler=self._list_tubes_reply_cb,
            error_handler=self._list_tubes_error_cb)
        self.is_initiator = False
        self.activity.board.set_sensitive(True)

    def _sharing_setup(self):
        if self.activity._shared_activity is None:
            logger.error('Failed to share or join activity')
            return

        self.conn = self.activity._shared_activity.telepathy_conn
        self.tubes_chan = self.activity._shared_activity.telepathy_tubes_chan
        self.text_chan = self.activity._shared_activity.telepathy_text_chan

        self.tubes_chan[
            TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].connect_to_signal(
                'NewTube', self._new_tube_cb)

        self.activity._shared_activity.connect(
            'buddy-joined', self._buddy_joined_cb)
        self.activity._shared_activity.connect(
            'buddy-left', self._buddy_left_cb)

        # Optional - included for example:
        # Find out who's already in the shared activity:
        for buddy in self.activity._shared_activity.get_joined_buddies():
            logger.debug(
                'Buddy %s is already in the activity',
                buddy.props.nick)

    def participant_change_cb(self, added, removed):
        logger.debug('Tube: Added participants: %r', added)
        logger.debug('Tube: Removed participants: %r', removed)
        for handle, bus_name in added:
            buddy = self._get_buddy(handle)
            if buddy is not None:
                logger.debug(
                    'Tube: Handle %u (Buddy %s) was added',
                    handle, buddy.props.nick)
        for handle in removed:
            buddy = self._get_buddy(handle)
            if buddy is not None:
                logger.debug('Buddy %s was removed' % buddy.props.nick)
        if not self.entered:
            if self.is_initiator:
                logger.debug(
                    "I'm initiating the tube, will watch for hellos.")
                self.add_hello_handler()
            else:
                logger.debug('Hello, everyone! What did I miss?')
                self.Hello()
        self.entered = True

    # This is sent to all participants whenever we join an activity
    @signal(dbus_interface=IFACE, signature='')
    def Hello(self):
        """Say Hello to whoever else is in the tube."""
        logger.debug('I said Hello.')

    # This is called by whoever receives our Hello signal
    # This method receives the current game state and puts us in sync
    # with the rest of the participants.
    # The current game state is represented by the game object
    @method(dbus_interface=IFACE, in_signature='a(ii)si', out_signature='')
    def World(self, undostack, taken_color, size):
        """To be called on the incoming XO after they Hello."""
        if not self.world:
            logger.debug(
                'Somebody called World and sent me undostack: %s', undostack)
            self.activity.board_size_change(None, size)
            self.bootstrap(list(undostack))
            self.activity.set_player_color(
                self.activity.invert_color(taken_color))
            # self.players = players
            # now I can World others
            self.add_hello_handler()
        else:
            self.world = True
            logger.debug("I've already been welcomed, doing nothing")

    @signal(dbus_interface=IFACE, signature='ii')
    def Play(self, x, y):
        """Say Hello to whoever else is in the tube."""
        logger.debug('Signaling players of stone placement at:%s x %s.', x, y)

    def add_hello_handler(self):
        logger.debug('Adding hello handler.')
        self.tube.add_signal_receiver(
            self.hello_signal_cb, 'Hello',
            IFACE, path=PATH, sender_keyword='sender')
        self.tube.add_signal_receiver(
            self.play_signal_cb, 'Play',
            IFACE, path=PATH, sender_keyword='sender')

    def hello_signal_cb(self, sender=None):
        """Somebody Helloed me. World them."""
        if sender == self.tube.get_unique_name():
            # sender is my bus name, so ignore my own signal
            return
        logger.debug('Newcomer %s has joined', sender)
        logger.debug('Welcoming newcomer and sending them the game state')
        # Strip the undostack to reduce net traffic =)
        strippedstack = []
        for pos, color, captures in self.undostack:
            strippedstack.append(pos)
        # FIXME: A spectator needs to send the color
        # that was taken, not its own
        self.tube.get_object(sender, PATH).World(
            strippedstack, self.activity.get_playercolor(),
            self.activity.size, dbus_interface=IFACE)
        self.activity.board.set_sensitive(True)

    def play_signal_cb(self, x, y, sender=None):
        """Somebody placed a stone. """
        if sender == self.tube.get_unique_name():
            # sender is my bus name, so ignore my own signal
            return
        logger.debug('Buddy %s placed a stone at %s x %s', sender, x, y)
        # Call our Play callback
        self.Play_cb(x, y, sender)

    def _list_tubes_error_cb(self, e):
        logger.error('ListTubes() failed: %s', e)

    def _list_tubes_reply_cb(self, tubes):
        for tube_info in tubes:
            self._new_tube_cb(*tube_info)

    def _new_tube_cb(self, id, initiator, type, service, params, state):
        logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
                     'params=%r state=%d', id, initiator, type, service,
                     params, state)
        if (type == TelepathyGLib.TubeType.DBUS and
                service == SERVICE):
            if state == TelepathyGLib.TubeState.LOCAL_PENDING:
                self.tubes_chan[
                    TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
            self.tube = SugarTubeConnection(
                self.conn,
                self.tubes_chan[TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES],
                id,
                group_iface=self.text_chan[
                    TelepathyGLib.IFACE_CHANNEL_INTERFACE_GROUP])
            super(CollaborationWrapper, self).__init__(self.tube, PATH)
            self.tube.watch_participants(self.participant_change_cb)

    def _buddy_joined_cb(self, activity, buddy):
        """Called when a buddy joins the shared activity. """
        logger.debug('Buddy %s joined', buddy.props.nick)
        self.buddy_joined(buddy)

    def _buddy_left_cb(self, activity, buddy):
        """Called when a buddy leaves the shared activity. """
        self.buddy_left(buddy)

    def _get_buddy(self, cs_handle):
        """Get a Buddy from a channel specific handle."""
        logger.debug('Trying to find owner of handle %u...', cs_handle)
        group = self.text_chan[TelepathyGLib.IFACE_CHANNEL_INTERFACE_GROUP]
        my_csh = group.GetSelfHandle()
        logger.debug('My handle in that group is %u', my_csh)
        if my_csh == cs_handle:
            handle = self.conn.GetSelfHandle()
            logger.debug('CS handle %u belongs to me, %u', cs_handle, handle)
        elif group.GetGroupFlags() & \
                TelepathyGLib.ChannelGroupFlags.CHANNEL_SPECIFIC_HANDLES:
            handle = group.GetHandleOwners([cs_handle])[0]
            logger.debug('CS handle %u belongs to %u', cs_handle, handle)
        else:
            handle = cs_handle
            logger.debug('non-CS handle %u belongs to itself', handle)
            # XXX: deal with failure to get the handle owner
            assert handle != 0
        return self.presence_service.get_buddy_by_telepathy_handle(
            self.conn.service_name, self.conn.object_path, handle)
Exemple #6
0
class ShareableActivity(activity.Activity):
    '''
    A shareable activity.

    Signals to connect to for more notifications:
        self.get_shared_activity().connect('buddy-joined', ...)
        self.get_shared_activity().connect('buddy-left', ...)
    '''
    def __init__(self, handle, *args, **kwargs):
        '''
        Initialize the ShareableActivity class.

        Kwargs:
            service_path
        '''

        activity.Activity.__init__(self, handle, *args, **kwargs)

        self._sync_hid = None
        self._message_cbs = {}

        self._connection = None
        self._tube_conn = None

        self._pservice = presenceservice.get_instance()
        self._owner = self._pservice.get_owner()
        self._owner_id = str(self._owner.props.nick)

        self._service_path = kwargs.get('service_path',
                                        self._generate_service_path())
        self._dbus_object = None

        _logger.debug('Setting service name %s, service path %s', IFACE,
                      self._service_path)

        self._connect_to_ps()

    def get_shared_activity(self):
        '''Get shared_activity object; works for different API versions.'''
        try:
            return self.shared_activity
        except:
            return self._shared_activity

    def get_owner(self):
        '''Return buddy object of the owner.'''
        return self._owner

    def get_owner_id(self):
        '''Return id (nickname) of the owner.'''
        return self._owner_id

    def get_bus_name(self):
        '''
        Return the DBus bus name for the tube we're using, or None if there
        is no tube yet.
        '''
        if self._tube_conn is not None:
            return self._tube_conn.get_unique_name()
        else:
            return None

    def _generate_service_path(self):
        bundle_id = self.get_bundle_id()
        last = bundle_id.split('.')[-1]
        instance_id = self.get_id()
        return '/org/laptop/ShareableActivity/%s/%s' % (last, instance_id)

    def _connect_to_ps(self):
        '''
        Connect to the presence service.
        '''
        if self.get_shared_activity():
            self.connect('joined', self._sa_joined_cb)
            if self.get_shared():
                self._sa_joined_cb()
        else:
            self.connect('shared', self._sa_shared_cb)

    def _setup_shared_activity(self):
        '''
        Setup sharing stuff: get channels etc.
        '''

        sa = self.get_shared_activity()
        if sa is None:
            _logger.error('_setup_shared_activity(): no shared_activity yet!')
            return False

        self._connection = sa.telepathy_conn
        self._tubes_chan = sa.telepathy_tubes_chan
        self._text_chan = sa.telepathy_text_chan

        self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

    def _sa_shared_cb(self, activity):
        self._setup_shared_activity()
        self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(IFACE, {})

    def _sa_joined_cb(self, activity):
        """Callback for when we join an existing activity."""

        _logger.info('Joined existing activity')
        self._request_sync = True
        self._setup_shared_activity()

        self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
            reply_handler=self._list_tubes_reply_cb,
            error_handler=self._list_tubes_error_cb)

    def _list_tubes_reply_cb(self, tubes):
        """Callback for when requesting an existing tube"""
        _logger.debug('_list_tubes_reply_cb(): %r', tubes)
        for tube_info in tubes:
            self._new_tube_cb(*tube_info)

    def _list_tubes_error_cb(self, e):
        _logger.error('ListTubes() failed: %s', e)

    def _new_tube_cb(self, id, initiator, type, service, params, state):
        _logger.debug(
            'New tube: ID=%d initator=%d type=%d service=%s '
            'params=%r state=%d', id, initiator, type, service, params, state)

        if (type == telepathy.TUBE_TYPE_DBUS and service == IFACE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(
                    id)

            self._tube_conn = SugarTubeConnection(
                self._connection,
                self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
                id,
                group_iface=self._text_chan[telepathy.CHANNEL_INTERFACE_GROUP])

            self._tube_conn.add_signal_receiver(self._send_message_cb,
                                                'SendMessage',
                                                sender_keyword='sender')

            self._dbus_object = ShareableObject(self._tube_conn,
                                                self._service_path)

    def buddy_joined(self, activity, buddy):
        '''
        Override to take action when a buddy joins.
        '''
        _logger.debug('Buddy joined: %s', buddy)

    def buddy_left(self, activity, buddy):
        '''
        Override to take action when a buddy left.
        '''
        _logger.debug('Buddy left: %s', buddy)

    def connect_message(self, msg, func):
        '''
        Connect function 'func' so that it's called when message <msg>
        is received. The function will receive keyword arguments sent
        with the message.
        '''
        self._message_cbs[msg] = func

    def message_received(self, msg, **kwargs):
        '''
        Override to take action when a message is received.
        This function will not be called for message handlers already
        registered with connect_message().
        '''
        _logger.debug('Received message: %s(%r)', msg, kwargs)

    def send_message(self, msg, **kwargs):
        '''
        Send a message to all connected buddies.
        '''
        if self._dbus_object is not None:
            _logger.debug('Sending message: %s(%r)', msg, kwargs)
            self._dbus_object.SendMessage(msg, kwargs)
        else:
            _logger.debug('Not shared, not sending message %s(%r)', msg,
                          kwargs)

    def send_message_to(self, buddy, msg, **kwargs):
        '''
        Send a message to one particular buddy.
        '''
        if self._dbus_object is not None:
            _logger.debug('Sending message to %s: %s(%r)', buddy, msg, kwargs)
            # FIXME: convert to busname
            self._dbus_object.SendMessageTo(buddy, msg, kwargs)
        else:
            _logger.debug('Not shared, not sending message %s(%r) to %s', msg,
                          kwargs, buddy)

    def _dispatch_message(self, msg, kwargs):
        passkwargs = {}
        for k, v in kwargs.iteritems():
            passkwargs[str(k)] = v

        if msg in self._message_cbs:
            func = self._message_cbs[msg]
            func(**passkwargs)
        else:
            self.message_received(msg, **passkwargs)

    def _send_message_cb(self, msg, kwargs, sender=None):
        '''Callback to filter message signals.'''
        _logger.debug('Sender: %s, owner: %s, owner_id: %s, busname: %s',
                      sender, self.get_owner(), self.get_owner_id(),
                      self.get_bus_name())
        if sender == self.get_bus_name():
            return
        kwargs['sender'] = sender
        self._dispatch_message(msg, kwargs)

    def _send_message_to_cb(self, to, msg, kwargs, sender=None):
        '''Callback to filter message signals.'''
        if to != self.get_bus_name():
            return
        kwargs['sender'] = sender
        kwargs['to'] = to
        self._dispatch_message(msg, kwargs)

    # FIXME: build a standard system to sync state from a single buddy
    def request_sync(self):
        if self._sync_hid is not None:
            return

        self._syncreq_buddy = 0
        self._sync_hid = GObject.timeout_add(2000, self._request_sync_cb)
        self._request_sync_cb()

    def _request_sync_cb(self):
        if self._syncreq_buddy <= len(self._connected_buddies):
            self._sync_hid = None
            return False

        self._syncreq_buddy += 1
class CollaborationWrapper(ExportedGObject):

    ''' A wrapper for the collaboration bureaucracy'''

    def __init__(self, activity, buddy_joined_cb, buddy_left_cb, play_cb, undostack, bootstrap):
        self.activity = activity
        self.buddy_joined = buddy_joined_cb
        self.buddy_left = buddy_left_cb
        self.Play_cb = play_cb
        self.undostack = undostack
        self.bootstrap = bootstrap
        self.world = False
        self.entered = False
        self.presence_service = presenceservice.get_instance() 
        self.owner = self.presence_service.get_owner()
        
    def _shared_cb(self, activity):
        self.activity.gameToolbar.grey_out_size_change()
        self.activity.gameToolbar.grey_out_restart()
        self.activity.gameToolbar.grey_out_ai()
        self.activity.undo_button.hide()
        self.activity.board.set_sensitive(False)
        self._sharing_setup()
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
            SERVICE, {})
        self.is_initiator = True
            
    def _joined_cb(self, activity):
        self.activity.gameToolbar.grey_out_size_change()
        self.activity.gameToolbar.grey_out_restart()
        self.activity.gameToolbar.grey_out_ai()
        self.activity.undo_button.hide()
        self.activity.board.set_sensitive(False)
        self._sharing_setup()
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
            reply_handler=self._list_tubes_reply_cb, 
            error_handler=self._list_tubes_error_cb)
        self.is_initiator = False
        self.activity.board.set_sensitive(True)
    
    def _sharing_setup(self):
        if self.activity._shared_activity is None:
            logger.error('Failed to share or join activity')
            return

        self.conn = self.activity._shared_activity.telepathy_conn
        self.tubes_chan = self.activity._shared_activity.telepathy_tubes_chan
        self.text_chan = self.activity._shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        self.activity._shared_activity.connect('buddy-joined', self._buddy_joined_cb)
        self.activity._shared_activity.connect('buddy-left', self._buddy_left_cb)

        # Optional - included for example:
        # Find out who's already in the shared activity:
        for buddy in self.activity._shared_activity.get_joined_buddies():
            logger.debug('Buddy %s is already in the activity',
                               buddy.props.nick)
                               
    def participant_change_cb(self, added, removed):
        logger.debug('Tube: Added participants: %r', added)
        logger.debug('Tube: Removed participants: %r', removed)
        for handle, bus_name in added:
            buddy = self._get_buddy(handle)
            if buddy is not None:
                logger.debug('Tube: Handle %u (Buddy %s) was added',
                                   handle, buddy.props.nick)
        for handle in removed:
            buddy = self._get_buddy(handle)
            if buddy is not None:
                logger.debug('Buddy %s was removed' % buddy.props.nick)
        if not self.entered:
            if self.is_initiator:
                logger.debug("I'm initiating the tube, will "
                    "watch for hellos.")
                self.add_hello_handler()
            else:
                logger.debug('Hello, everyone! What did I miss?')
                self.Hello()
        self.entered = True
        
        
    # This is sent to all participants whenever we join an activity
    @signal(dbus_interface=IFACE, signature='')
    def Hello(self):
        """Say Hello to whoever else is in the tube."""
        logger.debug('I said Hello.')
    
    # This is called by whoever receives our Hello signal
    # This method receives the current game state and puts us in sync
    # with the rest of the participants. 
    # The current game state is represented by the game object
    @method(dbus_interface=IFACE, in_signature='a(ii)si', out_signature='')
    def World(self, undostack, taken_color, size):
        """To be called on the incoming XO after they Hello."""
        if not self.world:
            logger.debug('Somebody called World and sent me undostack: %s',
                                undostack)
            self.activity.board_size_change(None, size)
            self.bootstrap(list(undostack))
            self.activity.set_player_color(self.activity.invert_color(taken_color))
            #self.players = players
            # now I can World others
            self.add_hello_handler()
        else:
            self.world = True
            logger.debug("I've already been welcomed, doing nothing")
    
    @signal(dbus_interface=IFACE, signature='ii')
    def Play(self, x, y):
        """Say Hello to whoever else is in the tube."""
        logger.debug('Signaling players of stone placement at:%s x %s.', x, y)
       
    def add_hello_handler(self):
        logger.debug('Adding hello handler.')
        self.tube.add_signal_receiver(self.hello_signal_cb, 'Hello', IFACE,
            path=PATH, sender_keyword='sender')
        self.tube.add_signal_receiver(self.play_signal_cb, 'Play', IFACE,
            path=PATH, sender_keyword='sender')
            
    def hello_signal_cb(self, sender=None):
        """Somebody Helloed me. World them."""
        if sender == self.tube.get_unique_name():
            # sender is my bus name, so ignore my own signal
            return
        logger.debug('Newcomer %s has joined', sender)
        logger.debug('Welcoming newcomer and sending them the game state')
        # Strip the undostack to reduce net traffic =)
        strippedstack = []
        for pos, color, captures in self.undostack:
            strippedstack.append(pos)
        # FIXME: A spectator needs to send the color that was taken, not its own
        self.tube.get_object(sender, PATH).World(strippedstack, 
                                                 self.activity.get_playercolor(), 
                                                 self.activity.size, 
                                                 dbus_interface=IFACE)
        self.activity.board.set_sensitive(True)
        
    def play_signal_cb(self, x, y, sender=None):
        """Somebody placed a stone. """
        if sender == self.tube.get_unique_name():
            # sender is my bus name, so ignore my own signal
            return
        logger.debug('Buddy %s placed a stone at %s x %s', sender, x, y)
        # Call our Play callback
        self.Play_cb(x, y, sender)

    def _list_tubes_error_cb(self, e):
        logger.error('ListTubes() failed: %s', e)
            
    def _list_tubes_reply_cb(self, tubes):
        for tube_info in tubes:
            self._new_tube_cb(*tube_info)
            
    def _new_tube_cb(self, id, initiator, type, service, params, state):
        logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
                     'params=%r state=%d', id, initiator, type, service,
                     params, state)
        if (type == telepathy.TUBE_TYPE_DBUS and
            service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
            self.tube = SugarTubeConnection(self.conn,
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
                id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
            super(CollaborationWrapper, self).__init__(self.tube, PATH)
            self.tube.watch_participants(self.participant_change_cb)

    def _buddy_joined_cb (self, activity, buddy):
        """Called when a buddy joins the shared activity. """
        logger.debug('Buddy %s joined', buddy.props.nick)
        self.buddy_joined(buddy)

    def _buddy_left_cb (self, activity, buddy):
        """Called when a buddy leaves the shared activity. """
        self.buddy_left(buddy)

    def _get_buddy(self, cs_handle):
        """Get a Buddy from a channel specific handle."""
        logger.debug('Trying to find owner of handle %u...', cs_handle)
        group = self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP]
        my_csh = group.GetSelfHandle()
        logger.debug('My handle in that group is %u', my_csh)
        if my_csh == cs_handle:
            handle = self.conn.GetSelfHandle()
            logger.debug('CS handle %u belongs to me, %u', cs_handle, handle)
        elif group.GetGroupFlags() & telepathy.CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
            handle = group.GetHandleOwners([cs_handle])[0]
            logger.debug('CS handle %u belongs to %u', cs_handle, handle)
        else:
            handle = cs_handle
            logger.debug('non-CS handle %u belongs to itself', handle)
            # XXX: deal with failure to get the handle owner
            assert handle != 0
        return self.presence_service.get_buddy_by_telepathy_handle(
            self.conn.service_name, self.conn.object_path, handle)
class ShareableActivity(activity.Activity):

    '''
    A shareable activity.

    Signals to connect to for more notifications:
        self.get_shared_activity().connect('buddy-joined', ...)
        self.get_shared_activity().connect('buddy-left', ...)
    '''

    def __init__(self, handle, *args, **kwargs):
        '''
        Initialize the ShareableActivity class.

        Kwargs:
            service_path
        '''

        activity.Activity.__init__(self, handle, *args, **kwargs)

        self._sync_hid = None
        self._message_cbs = {}

        self._connection = None
        self._tube_conn = None

        self._pservice = presenceservice.get_instance()
        self._owner = self._pservice.get_owner()
        self._owner_id = str(self._owner.props.nick)

        self._service_path = kwargs.get('service_path',
                                        self._generate_service_path())
        self._dbus_object = None

        _logger.debug('Setting service name %s, service path %s',
                      IFACE, self._service_path)

        self._connect_to_ps()

    def get_shared_activity(self):
        '''Get shared_activity object; works for different API versions.'''
        try:
            return self.shared_activity
        except:
            return self._shared_activity

    def get_owner(self):
        '''Return buddy object of the owner.'''
        return self._owner

    def get_owner_id(self):
        '''Return id (nickname) of the owner.'''
        return self._owner_id

    def get_bus_name(self):
        '''
        Return the DBus bus name for the tube we're using, or None if there
        is no tube yet.
        '''
        if self._tube_conn is not None:
            return self._tube_conn.get_unique_name()
        else:
            return None

    def _generate_service_path(self):
        bundle_id = self.get_bundle_id()
        last = bundle_id.split('.')[-1]
        instance_id = self.get_id()
        return '/org/laptop/ShareableActivity/%s/%s' % (last, instance_id)

    def _connect_to_ps(self):
        '''
        Connect to the presence service.
        '''
        if self.get_shared_activity():
            self.connect('joined', self._sa_joined_cb)
            if self.get_shared():
                self._sa_joined_cb()
        else:
            self.connect('shared', self._sa_shared_cb)

    def _setup_shared_activity(self):
        '''
        Setup sharing stuff: get channels etc.
        '''

        sa = self.get_shared_activity()
        if sa is None:
            _logger.error('_setup_shared_activity(): no shared_activity yet!')
            return False

        self._connection = sa.telepathy_conn
        self._tubes_chan = sa.telepathy_tubes_chan
        self._text_chan = sa.telepathy_text_chan

        self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

    def _sa_shared_cb(self, activity):
        self._setup_shared_activity()
        self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
            IFACE, {})

    def _sa_joined_cb(self, activity):
        """Callback for when we join an existing activity."""

        _logger.info('Joined existing activity')
        self._request_sync = True
        self._setup_shared_activity()

        self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
            reply_handler=self._list_tubes_reply_cb,
            error_handler=self._list_tubes_error_cb)

    def _list_tubes_reply_cb(self, tubes):
        """Callback for when requesting an existing tube"""
        _logger.debug('_list_tubes_reply_cb(): %r', tubes)
        for tube_info in tubes:
            self._new_tube_cb(*tube_info)

    def _list_tubes_error_cb(self, e):
        _logger.error('ListTubes() failed: %s', e)

    def _new_tube_cb(self, id, initiator, type, service, params, state):
        _logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
                      'params=%r state=%d', id, initiator, type, service,
                      params, state)

        if (type == telepathy.TUBE_TYPE_DBUS and service == IFACE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self._tubes_chan[
                    telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self._tube_conn = SugarTubeConnection(
                self._connection, self._tubes_chan[
                    telepathy.CHANNEL_TYPE_TUBES],
                id, group_iface=self._text_chan[
                    telepathy.CHANNEL_INTERFACE_GROUP])

            self._tube_conn.add_signal_receiver(
                self._send_message_cb,
                'SendMessage', sender_keyword='sender')

            self._dbus_object = ShareableObject(self._tube_conn,
                                                self._service_path)

    def buddy_joined(self, activity, buddy):
        '''
        Override to take action when a buddy joins.
        '''
        _logger.debug('Buddy joined: %s', buddy)

    def buddy_left(self, activity, buddy):
        '''
        Override to take action when a buddy left.
        '''
        _logger.debug('Buddy left: %s', buddy)

    def connect_message(self, msg, func):
        '''
        Connect function 'func' so that it's called when message <msg>
        is received. The function will receive keyword arguments sent
        with the message.
        '''
        self._message_cbs[msg] = func

    def message_received(self, msg, **kwargs):
        '''
        Override to take action when a message is received.
        This function will not be called for message handlers already
        registered with connect_message().
        '''
        _logger.debug('Received message: %s(%r)', msg, kwargs)

    def send_message(self, msg, **kwargs):
        '''
        Send a message to all connected buddies.
        '''
        if self._dbus_object is not None:
            _logger.debug('Sending message: %s(%r)', msg, kwargs)
            self._dbus_object.SendMessage(msg, kwargs)
        else:
            _logger.debug('Not shared, not sending message %s(%r)',
                          msg, kwargs)

    def send_message_to(self, buddy, msg, **kwargs):
        '''
        Send a message to one particular buddy.
        '''
        if self._dbus_object is not None:
            _logger.debug('Sending message to %s: %s(%r)', buddy, msg, kwargs)
            # FIXME: convert to busname
            self._dbus_object.SendMessageTo(buddy, msg, kwargs)
        else:
            _logger.debug('Not shared, not sending message %s(%r) to %s',
                          msg, kwargs, buddy)

    def _dispatch_message(self, msg, kwargs):
        passkwargs = {}
        for k, v in kwargs.iteritems():
            passkwargs[str(k)] = v

        if msg in self._message_cbs:
            func = self._message_cbs[msg]
            func(**passkwargs)
        else:
            self.message_received(msg, **passkwargs)

    def _send_message_cb(self, msg, kwargs, sender=None):
        '''Callback to filter message signals.'''
        _logger.debug(
            'Sender: %s, owner: %s, owner_id: %s, busname: %s', sender,
            self.get_owner(), self.get_owner_id(), self.get_bus_name())
        if sender == self.get_bus_name():
            return
        kwargs['sender'] = sender
        self._dispatch_message(msg, kwargs)

    def _send_message_to_cb(self, to, msg, kwargs, sender=None):
        '''Callback to filter message signals.'''
        if to != self.get_bus_name():
            return
        kwargs['sender'] = sender
        kwargs['to'] = to
        self._dispatch_message(msg, kwargs)

    # FIXME: build a standard system to sync state from a single buddy
    def request_sync(self):
        if self._sync_hid is not None:
            return

        self._syncreq_buddy = 0
        self._sync_hid = GObject.timeout_add(2000, self._request_sync_cb)
        self._request_sync_cb()

    def _request_sync_cb(self):
        if self._syncreq_buddy <= len(self._connected_buddies):
            self._sync_hid = None
            return False

        self._syncreq_buddy += 1