1 """
2 a module to make sharing easier in Sugar Activities.
3 """
4
5 import logging
6 import telepathy
7
8 from sugar.activity.activity import Activity, ActivityToolbox
9 from sugar.presence import presenceservice
10
11 from sugar.presence.tubeconn import TubeConnection
12 from sugar.graphics.window import Window
13
14 import gtk
15 import gobject
16
17 import groupthink_base as groupthink
18
20 while gtk.events_pending():
21 gtk.main_iteration()
22
24 """
25 An abstract class for Activities using Groupthink.
26 Activity authors who are writing a shared Activity should consider
27 inheriting from GroupActivity instead of Sugar's standard Activity class.
28 GroupActivity automates (and hides) handling of Telepathy tubes. It also
29 initializes a L{Group} and a L{TimeHandler} allowing shared activities to
30 be written with minimal boilerplate. For example, the following is a
31 working shared text editor using GroupActivity::
32 from groupthink import sugar_tools, gtk_tools
33 import sugar
34 class SharedTextDemoActivity(sugar_tools.GroupActivity):
35 def initialize_display(self):
36 self.cloud.textview = gtk_tools.SharedTextView()
37 return self.cloud.textview
38 In addition to sharing code, GroupActivity also provides subclasses with
39 more informative startup screens and optionally automated save/load to the
40 datastore.
41
42 Caution: The methods required of a subclass of GroupActivity differ
43 substantially from the methods required of a subclass of Activity. For
44 example, subclasses of GroupActivity typically do not need to implement
45 a __init__ method.
46 """
47
48 message_preparing = "Preparing user interface"
49 message_loading = "Loading object from Journal"
50 message_joining = "Joining shared activity"
51
53
54
55
56
57 self.initiating = False
58
59 self._processed_share = False
60
61 self.initialized = False
62
63 self.early_setup()
64
65 super(GroupActivity, self).__init__(handle)
66 self.dbus_name = self.get_bundle_id()
67 self.logger = logging.getLogger(self.dbus_name)
68
69 self._handle = handle
70
71
72
73 self._sharing_completed = not self._shared_activity
74 self._readfile_completed = not handle.object_id
75 if self._shared_activity:
76 self.message = self.message_joining
77 elif handle.object_id:
78 self.message = self.message_loading
79 else:
80 self.message = self.message_preparing
81
82
83 toolbox = ActivityToolbox(self)
84 self.set_toolbox(toolbox)
85 toolbox.show()
86
87 v = gtk.VBox()
88 self.startup_label = gtk.Label(self.message)
89 v.pack_start(self.startup_label)
90 Window.set_canvas(self,v)
91 self.show_all()
92
93
94
95
96
97 exhaust_event_loop()
98
99
100
101
102 self.tubebox = groupthink.TubeBox()
103 self.timer = groupthink.TimeHandler("main", self.tubebox)
104 self.cloud = groupthink.Group(self.tubebox)
105
106
107
108
109
110
111
112 self.pservice = presenceservice.get_instance()
113
114 owner = self.pservice.get_owner()
115 self.owner = owner
116
117 self.connect('shared', self._shared_cb)
118 self.connect('joined', self._joined_cb)
119 if self.get_shared():
120 if self.initiating:
121 self._shared_cb(self)
122 else:
123 self._joined_cb(self)
124
125 self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
126 self.connect("visibility-notify-event", self._visible_cb)
127 self.connect("notify::active", self._active_cb)
128
129 if not self._readfile_completed:
130 self.read_file(self._jobject.file_path)
131 elif not self._shared_activity:
132 gobject.idle_add(self._initialize_cleanstart)
133
138
140 """
141 Any subclass that needs to take any extra action in the case where
142 the activity is launched locally without a sharing context or input
143 file should override this method"""
144 pass
145
147 """
148 Any subclass that needs to take an action before any external interaction
149 (e.g. read_file, write_file) occurs should place that code in early_setup"""
150 pass
151
153 main_widget = self.initialize_display()
154 Window.set_canvas(self, main_widget)
155 self.initialized = True
156 if self._shared_activity and not self._processed_share:
157
158
159 self.when_shared()
160 self._processed_share = True
161 self.show_all()
162
164 """
165 All subclasses must override this method, which is the principal
166 means of initializing a GroupActivity.
167 @rtype: gtk.Widget
168 @return: The widget that will be the display for this activity (i.e.
169 the canvas)."""
170 raise NotImplementedError
171
172 - def share(self, private=False):
173 """
174 The purpose of this function is solely to permit us to determine
175 whether share() has been called. This is necessary because share() may
176 be called during Activity.__init__, and thus emit the 'shared' signal
177 before we have a chance to connect any signal handlers."""
178 self.initiating = True
179 super(GroupActivity, self).share(private)
180 if self.initialized and not self._processed_share:
181 self.when_shared()
182 self._processed_share = True
183
185 """
186 Inheritors should override this method to perform any special
187 operations when the user shares the session"""
188 pass
189
191 """
192 Inheritors should override this method to perform any special
193 operations upon initiating sharing. This method will not be called
194 for "joiners", only for "sharers"."""
195 pass
196
198 self.logger.debug('My activity was shared')
199 self.initiating = True
200 self._sharing_setup()
201
202 self.logger.debug('This is my activity: making a tube...')
203 id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
204 self.dbus_name, {})
205 self.when_initiating_sharing()
206
208 if self._shared_activity is None:
209 self.logger.error('Failed to share or join activity')
210 return
211
212 self.conn = self._shared_activity.telepathy_conn
213 self.tubes_chan = self._shared_activity.telepathy_tubes_chan
214 self.text_chan = self._shared_activity.telepathy_text_chan
215
216 self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
217 self._new_tube_cb)
218
220 self.logger.debug('Got %d tubes from ListTubes' % len(tubes))
221 for tube_info in tubes:
222 self._new_tube_cb(*tube_info)
223
225 self.logger.error('ListTubes() failed: %s', e)
226
228 if not self._shared_activity:
229 return
230
231 self.logger.debug('Joined an existing shared activity')
232 self.initiating = False
233 self._sharing_setup()
234
235 self.logger.debug('This is not my activity: waiting for a tube...')
236 self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
237 reply_handler=self._list_tubes_reply_cb,
238 error_handler=self._list_tubes_error_cb)
239
240 - def _new_tube_cb(self, id, initiator, type, service, params, state):
241 self.logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
242 'params=%r state=%d', id, initiator, type, service,
243 params, state)
244 if (type == telepathy.TUBE_TYPE_DBUS and
245 service == self.dbus_name):
246 if state == telepathy.TUBE_STATE_LOCAL_PENDING:
247 self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
248 tube_conn = TubeConnection(self.conn,
249 self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
250 id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
251 self.tubebox.insert_tube(tube_conn, self.initiating)
252 self._sharing_completed = True
253 if self._readfile_completed and not self.initialized:
254 self._initialize_display()
255
262
264 """
265 Inheritors wishing to control file saving should override this method.
266 Any inheritor overriding this method must return the
267 string provided to save_to_journal as cloudstring.
268 The default implementation of load_from_journal simply returns the contents
269 of the file, matching the default implementation of save_to_journal.
270 @type file_path: str
271 @param file_path: path to the file to read
272 @rtype: str
273 @return: a string previously passed to save_to_journal as cloudstring"""
274 if file_path:
275 f = file(file_path,'rb')
276 s = f.read()
277 f.close()
278 return s
279
281
282
283
284
285
286 if not self._readfile_completed:
287 self.read_file(self._jobject.file_path)
288 self.save_to_journal(file_path, self.cloud.dumps())
289
291 """Any inheritor who wishes to control file
292 output should override this method, and must
293 be sure to include cloudstring in its write_file.
294 The default implementation of save_to_journal simply dumps the output of
295 self.cloud.dumps() to disk.
296 @type file_path: str
297 @param file_path: the path to which the activity should write
298 @type cloudstring: str
299 @param cloudstring: an additional string representing the state of all
300 objects associated with self.cloud. This string may be saved
301 as the inheritor sees fit."""
302 f = file(file_path, 'wb')
303 f.write(cloudstring)
304 f.close()
305
307 self.logger.debug("_active_cb")
308 if self.props.active:
309 self.resume()
310 else:
311 self.pause()
312
314 self.logger.debug("_visible_cb")
315 if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED:
316 self.pause()
317 else:
318 self.resume()
319
321 """
322 This method will be called when the display is not visible.
323 Subclasses should override this function to stop updating the display
324 when it is not visible."""
325 pass
326
328 """
329 This method will be called when the display becomes visible.
330 Subclasses should override this function to resume updating the
331 display, since it is now visible"""
332 pass
333