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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
|
#!/usr/bin/env python
# Copyright (C) 2009, George Hunt <georgejhunt@gmail.com>
# Copyright (C) 2009, One Laptop Per Child
#
# 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
from __future__ import with_statement
import os, os.path, ConfigParser, shutil, sys
from subprocess import Popen, PIPE
from gettext import gettext as _
#major packages
import gtk
import time
import datetime
import gobject
from fnmatch import fnmatch
#sugar stuff
import sugar.env
from sugar.datastore import datastore
from sugar.graphics.alert import *
import sugar.activity.bundlebuilder as bundlebuilder
#build 650 doesn't include fix_manifest
#import bundlebuilder
from sugar.bundle.activitybundle import ActivityBundle
#following only works in sugar 0.82
#from sugar.activity.registry import get_registry
from sugar.activity.activity import Activity
from sugar import profile
#import logging
from pydebug_logging import _logger, log_environment, log_dict
class ProjectFunctions:
def __init__(self,activity):
self._activity = activity
self._load_to_playpen_source = None
def get_editor(self):
raise NotImplimentedError
def write_binary_to_datastore(self):
"""
Check to see if there is a child loaded.
then bundle it up and write it to the journal
lastly serialize the project information and write it to the journal
"""
if self._activity.child_path == None: return
dist_dir = os.path.join(self._activity.child_path,'dist')
try:
os.rmtree(dist_dir)
except:
_logger.debug('failed to rmtree %s'%dist_dir)
try:
os.mkdir(dist_dir)
except:
_logger.debug('failed to os.mkdir %s'%dist_dir)
#are there changes in edit buffers not flushed to disk?
self.get_editor().save_all()
#remove any embeded shell breakpoints
self.get_editor().clear_embeds()
#create the manifest for the bundle
manifest_ok = self.write_manifest()
#see if this bundle passes the parse test
bundle = ActivityBundle(self._activity.child_path)
do_tgz = True
mime = self._activity.MIME_ZIP
activity = 'org.laptop.PyDebug'
#if manifest was successful, write the xo bundle to the instance directory
if bundle:
do_tgz = False
try:
#actually write the xo file
if self._activity.sugar_minor >= 82:
_logger.debug('making xo bundle from: %s'%(self._activity.child_path))
config = bundlebuilder.Config(self._activity.child_path)
packager = bundlebuilder.XOPackager(bundlebuilder.Builder(config))
packager.package()
xo_name = config.xo_name
source = os.path.join('dist', xo_name)
else:
#how to create the zipped xo file on build 650
name = self._activity.activity_dict.get('name','')
xo_name = bundlebuilder._get_package_name(name)
_logger.debug('zipped xo name:%s current dir: %s'%(xo_name, os.getcwd(),))
bundlebuilder.cmd_dist(name, 'MANIFEST')
source = xo_name
dest = os.path.join(self._activity.get_activity_root(),
'instance', xo_name)
_logger.debug('writing to the journal from %s to %s.'%(source,dest))
if os.path.isfile(dest):
os.unlink(dest)
try:
package = xo_name
shutil.copy(source,dest)
mime = self._activity.MIME_TYPE
activity = self._activity.activity_dict.get('activity','')
self.to_removable_bin(source)
except IOError:
_logger.debug('shutil.copy error %d: %s. ',IOError[0],IOError[1])
do_tgz = True
mime = self._activity.MIME_ZIP
except Exception, e:
_logger.exception('outer exception %r'%e)
do_tgz = True
else:
_logger.debug('unable to parse bundle with ActivityBundle object')
if do_tgz:
dest = self.just_do_tar_gz()
if dest:
package = os.path.basename(dest)
dsobject = datastore.create()
dsobject.metadata['package'] = package
dsobject.metadata['title'] = package
dsobject.metadata['mime_type'] = mime
dsobject.metadata['icon'] = self._activity.activity_dict.get('icon','')
dsobject.metadata['bundle_id'] = self._activity.activity_dict.get('bundle_id','')
dsobject.metadata['activity'] = activity
dsobject.metadata['version'] = self._activity.activity_dict.get('version',1)
#calculate and store the new md5sum
dsobject.metadata['tree_md5'] = self.save_tree_md5(self._activity.child_path)
self._activity.debug_dict['tree_md5'] = dsobject.metadata['tree_md5']
if dest: dsobject.set_file_path(dest)
#actually make the call which writes to the journal
try:
datastore.write(dsobject,transfer_ownership=True)
_logger.debug('succesfully wrote to the journal from %s.'%(dest))
except Exception, e:
_logger.error('datastore.write exception %r'%e)
return
#update the project display
if self.journal_class:
self.journal_class.new_directory()
def save_tree_md5(self, path):
#calculate and store the new md5sum
self._activity.debug_dict['tree_md5'] = self._activity.util.md5sum_tree(self._activity.child_path)
return self._activity.debug_dict['tree_md5']
def removable_backup(self):
""" if there is a pydebug folder in root directory of a USB or SD, this
routine will copy the debugee source tree to a folder name which includes
the datetime
"""
rs = self.removable_storage()
_logger.debug('removable storage %r'%rs)
for dest in rs:
root = os.path.join(dest,'pydebug')
if os.path.isdir(root): #there is a pydebug directory in the root of this device
today = datetime.date.today()
name = self._activity.child_path.split('/')[-1].split(".")[0] + '-' + str(today)
#change name if necessary to prevent collision
basename = self._activity.util.non_conflicting(root,name)
try:
shutil.copytree(self._activity.child_path, os.path.join(root,basename))
self._activity.util.set_permissions(os.path.join(root,basename))
except Exception, e:
_logger.error('copytree exception %r'%e)
def to_removable_bin(self, source):
for dest in self.removable_storage():
root = os.path.join(dest,'bin')
if not os.path.isdir(root): #there is no bin directory in the root of this device
try:
os.mkdir(root)
except Exception, e:
_logger.debug('mkdir exception %r'%e)
if os.path.isdir(root):
basename = os.path.basename(source)
target = os.path.join(root,basename)
if os.path.isfile(target):
os.unlink(target)
_logger.debug('copying %s to %s'%(source,target))
shutil.copyfile(source,target)
def removable_storage(self):
"""uses shell 'mount' cmd dto get mounted volumes
needs to be tested on various builds
"""
cmd = 'mount'
ret = []
resp, status = self._activity.util.command_line(cmd)
if status == 0:
for line in resp.split('\n'):
chunks = line.split()
if len(chunks) > 2:
if chunks[2].startswith('/media'):
if chunks[2] == '/media/Boot': continue
_logger.debug('mount point: %s'%chunks[2])
ret.append(chunks[2])
return ret
return None
def write_manifest(self):
""" use sugar routines to build a new MANIFEST file """
IGNORE_DIRS = ['dist', '.git']
IGNORE_FILES = ['.gitignore', 'MANIFEST', '*.pyc', '*~', '*.bak', 'pseudo.po']
try:
os.remove(os.path.join(self._activity.child_path,'MANIFEST'))
except:
pass
dest = self._activity.child_path
manifest = self.list_files(dest, IGNORE_DIRS, IGNORE_FILES)
_logger.debug('Writing manifest to %s.'%(dest))
try:
"""
config = bundlebuilder.Config(dest)
b = bundlebuilder.Builder(config)
b.fix_manifest()
"""
f = open(os.path.join(self._activity.child_path, "MANIFEST"), "wb")
for line in manifest:
f.write(line + "\n")
f.close()
except Exception, e:
_logger.debug('fix manifest error: %s'%(e,))
return False
return True
#following two functions lifted (slight mods) from bundlebuilder build 852
def fix_manifest(self):
f = open(os.path.join(self._activity.child_path, "MANIFEST"), "wb")
for line in manifest:
f.write(line + "\n")
def list_files(self, base_dir, ignore_dirs=None, ignore_files=None):
result = []
base_dir = os.path.abspath(base_dir)
for root, dirs, files in os.walk(base_dir):
if ignore_files:
for pattern in ignore_files:
files = [f for f in files if not fnmatch(f, pattern)]
rel_path = root[len(base_dir) + 1:]
for f in files:
result.append(os.path.join(rel_path, f))
if ignore_dirs and root == base_dir:
for ignore in ignore_dirs:
if ignore in dirs:
dirs.remove(ignore)
return result
def just_do_tar_gz(self):
"""
tar and compress the child_path tree to the journal
"""
name = self._activity.child_path.split('/')[-1].split(".")[0]+'.tar.gz'
os.chdir(self._activity.activity_playpen)
dest = os.path.join(self._activity.get_activity_root(),'instance',name)
cmd = 'tar czf %s %s'%(dest,'./'+os.path.basename(self._activity.child_path))
ans = self._activity.util.command_line(cmd)
_logger.debug('cmd:%s'%cmd)
if ans[1]!=0:
return None
return dest
def load_activity_to_playpen(self,file_path):
"""loads from a disk tree"""
self._new_child_path = os.path.join(self._activity.activity_playpen,os.path.basename(file_path))
_logger.debug('copying file for %s to %s'%(file_path,self._new_child_path))
self._load_playpen(file_path)
def try_to_load_from_journal(self,object_id):
"""
loads a zipped XO or tar.gz application file (tar.gz if bundler cannot parse the activity.info file)
"""
self.ds = datastore.get(object_id[0])
if not self.ds:
_logger.debug('failed to get datastore object with id:%s'%object_id[0])
return
dsdict=self.ds.get_metadata()
file_name_from_ds = self.ds.get_file_path()
project = dsdict.get('package','')
mime_type = dsdict.get('mime_type')
_logger.debug('load from journal, mime type: %s'%mime_type)
if not mime_type in [self._activity.MIME_TYPE, self._activity.MIME_ZIP,]:
self._activity.util.alert(_('This journal item does not appear to be a zipped activity. Package:%s.'%project))
self.ds.destroy()
self.ds = None
return
filestat = os.stat(file_name_from_ds)
size = filestat.st_size
_logger.debug('In try_to_load_from_journal. Object_id %s. File_path %s. Size:%s'%(object_id[0], file_name_from_ds, size))
if mime_type == self._activity.MIME_TYPE:
try:
self._bundler = ActivityBundle(file_name_from_ds)
name_with_blanks = self._bundler.get_name()
name = ''
for i in range(len(name_with_blanks)):
if name_with_blanks[i] == ' ': continue
name += name_with_blanks[i]
self._activity.activity_dict['name'] = name
iszip=True
istar = False
except:
self._activity.util.alert('Error: Malformed Activity Bundle')
self.ds.destroy()
self.ds = None
return
else:
name = project.split('.')[0]
#self.delete_after_load = os.path.abspath(file_name_from_ds,name)
iszip = False
istar = True
self._new_child_path = os.path.join(self._activity.activity_playpen,name+'.activity')
self._load_playpen(file_name_from_ds, iszip, istar)
def _load_playpen(self,source_fn, iszip = False, istar=False):
"""entry point for both xo and file tree sources"""
self._load_to_playpen_source = source_fn
#need to pass parameters to continuation routine
self.lp_iszip = iszip
self.lp_istar = istar
#if necessary clean up contents of playpen
#has the tree md5 changed?
if not self._activity.debug_dict.get('tree_md5','') == '':
tree_md5 = self._activity.util.md5sum_tree(self._activity.child_path)
if tree_md5 and tree_md5 != self._activity.debug_dict['tree_md5']:
action_prompt = _('Select OK to abandon changes to ') + \
os.path.basename(self._activity.child_path)
self._activity.util.confirmation_alert(action_prompt, \
_('Changes have been made to the PyDebug work area.'), \
self.continue_loading_playpen_cb)
return
self.continue_loading_playpen_cb(None, None)
def continue_loading_playpen_cb(self, alert, confirmation):
self._unload_playpen()
iszip = self.lp_iszip
istar = self.lp_istar
if self._load_to_playpen_source == None:
#having done the clearing, just stop
return
if iszip:
if self._activity.sugar_minor >= 84:
self._bundler.install(self._activity.activity_playpen)
else:
#self._bundler.install()
tmp_name = os.path.basename(self._new_child_path) + '.xo'
dest = os.path.join(self._activity.activity_playpen, tmp_name)
shutil.copy(self._load_to_playpen_source,dest)
os.chdir(self._activity.activity_playpen)
cmd = 'unzip -q %s'%dest
_logger.debug('loading XO file with cmd %s'%cmd)
rtn = self._activity.util.command_line(cmd)
if rtn[1] != 0: return
os.unlink(dest)
if self.ds:
self.ds.destroy()
self.ds = None
elif istar:
dsdict = self.ds.get_metadata()
project = dsdict.get('package','dummy.tar.gz')
name = project.split('.')[0]
dest = os.path.join(self._activity.activity_playpen,project)
shutil.copy(self._load_to_playpen_source,dest)
os.chdir(self._activity.activity_playpen)
cmd = 'tar zxf %s'%dest
_logger.debug('loading tar.gz with cmd %s'%cmd)
rtn = self._activity.util.command_line(cmd)
if rtn[1] != 0: return
os.unlink(dest)
if self.ds: self.ds.destroy()
self.ds = None
elif os.path.isdir(self._load_to_playpen_source):
#shutil.copy dies if the target exists, so rmtree if target exists
basename = self._load_to_playpen_source.split('/')[-1]
if basename.endswith('.activity'):
dest = self._new_child_path
else:
dest = os.path.join(self._activity.child_path,basename)
if os.path.isdir(dest):
shutil.rmtree(dest,ignore_errors=True)
#os.mkdir(dest)
_logger.debug('dest:%s'%dest)
_logger.debug('copying tree from %s to %s'%(self._load_to_playpen_source,dest))
shutil.copytree(self._load_to_playpen_source,dest)
_logger.debug('returned from copytree')
elif os.path.isfile(self._load_to_playpen_source):
source_basename = os.path.basename(self._load_to_playpen_source)
#dest = os.path.join(self._activity.child_path,source_basename)
dest = self._activity.child_path
_logger.debug('file copy from %s to %s'%(self._load_to_playpen_source, dest,))
shutil.copy(self._load_to_playpen_source,dest)
self._activity.debug_dict['source_tree'] = self._load_to_playpen_source
self._activity.child_path = self._new_child_path
self.setup_new_activity()
"""The following code needs to be copied inline to any routine which overwrites playpen
#has the tree md5 changed?
tree_md5 = self._activity.util.md5sum_tree(self._activity.child_path)
if tree_md5 != self._activity.debug_dict['tree_md5']:
action_prompt = _('Select OK to abandon changes to ') + \
os.path.basename(self._activity.child_path)
self._activity.util.confirmation_alert(action_prompt,
_('Changes have been made to the PyDebug work area.'),
self.continue_inline_cb)
"""
def _unload_playpen(self, rmtree = True):
#IPython gets confused if path it knows about suddenly disappears
#cmd = "cd '%s'\n"%self._activity.pydebug_path
os.environ['HOME'] = self._activity.debugger_home
cmd = "quit()\ngo\n"
self._activity.feed_virtual_terminal(0,cmd)
if self._activity.child_path and os.path.isdir(self._activity.child_path):
self.abandon_changes = True
#there is a on change call back to disable
self._activity.debug_dict['tree_md5'] = ''
self._activity.debug_dict['child_path'] = ''
self.get_editor().remove_all()
if rmtree:
shutil.rmtree(self._activity.child_path)
self.abandon_changes = False
#check to see if the Activity directory has a link to playpen
link_dir = os.path.join('/home/olpc/Activities',
os.path.basename(self._activity.child_path))
if os.path.islink(link_dir):
os.unlink(link_dir)
def copy_tree(self,source,dest):
if os.path.isdir(dest):
try:
shutil.rmtree(dest)
except Exception, error:
_logger.debug('rmtree exception %r'%error)
try:
shutil.copytree(source,dest)
self._activity.util.set_permissions(dest)
except Exception, error:
_logger.debug('copytree exception %r'%error)
def read_activity_info(self, path):
"""
Fetch the activity info from Datastore object and Sugar system calls
"""
try:
_logger.debug ('passed in file path: %s'%path)
bundle = ActivityBundle(path)
except Exception,e:
_logger.debug('exception %r'%e)
#msg = _('%s not recognized by ActivityBundle parser. Does activity/activity.info exist?'%os.path.basename(path))
#self._activity.util.alert(msg)
self._activity.init_activity_dict()
if self._activity.child_path and os.path.isdir(self._activity.child_path) and \
self._activity.child_path.endswith('.activity'):
name = os.path.basename(path).split('.')[0]
self._activity.activity_dict['name'] = name
self._activity.activity_dict['bundle_id'] = 'org.laptop.' + name
return #maybe should issue an alert here
self._activity.activity_dict['version'] = str(bundle.get_activity_version())
############################
#note to myself -- Made a decision I'm now forced to live with:
#bundle install appears to crunch blanks out of name when it installs
###########################
name_with_blanks = bundle.get_name()
name = ''
for i in range(len(name_with_blanks)):
if name_with_blanks[i] == ' ': continue
name += name_with_blanks[i]
self._activity.activity_dict['name'] = name
self._activity.activity_dict['bundle_id'] = bundle.get_bundle_id()
self._activity.activity_dict['command'] = bundle.get_command()
cmd_args = bundle.get_command()
self._activity.activity_dict['command'] = cmd_args
if cmd_args.startswith('sugar-activity'):
mod_class = cmd_args.split()[1]
if '.' in mod_class:
self._activity.activity_dict['class'] = mod_class.split('.')[1]
self._activity.activity_dict['module'] = mod_class.split('.')[0]
else:
self._activity.activity_dict['module'] = cmd_args
self._activity.activity_dict['class'] = ''
self._activity.activity_dict['icon'] = bundle.get_icon()
self._activity.activity_dict['title'] = 'PyDebug_' + self._activity.activity_dict['name']
log_dict(self._activity.activity_dict,'Contents of activity_dict')
self._activity.update_metadata()
|