Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/collaboration.py
blob: 56fef363ed072874500722a7d455146a2c550216 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# -*- coding: UTF-8 -*-
# Copyright 2007-2008 One Laptop Per Child
# Copyright 2007 Gerard J. Cerchio <www.circlesoft.com>
# Copyright 2008 Andrés Ambrois <andresambrois@gmail.com>
#
# 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

import logging
import sugar.logger

from sugar.presence import presenceservice
import telepathy
from dbus.service import method, signal
# In build 656 Sugar lacks sugartubeconn
try:
  from sugar.presence.sugartubeconn import SugarTubeConnection
except:
  from sugar.presence.tubeconn import TubeConnection
from dbus.gobject_service import ExportedGObject

SERVICE = "org.freedesktop.Telepathy.Tube.PlayGo"
IFACE = SERVICE
PATH = "/org/freedesktop/Telepathy/Tube/PlayGo"

logger = logging.getLogger('PlayGo')

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._sharing_setup()
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
            SERVICE, {})
        self.is_initiator = True
        self.activity.undo_button.hide()
            
    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._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.undo_button.hide()
    
    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)
        
    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)