Codebase list sugar-log-activity / 163b2b9
Imported Upstream version 6 Dipankar Patro 13 years ago
7 changed file(s) with 934 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 5
1
2 * Harden logcollect.py with a whole bunch of try...except to always collect as
3 much as possible. (pascal)
4
5 4
6
7 * logcollect.py now defaults to http://olpc.scheffers.net/olpc/submit.tcl
8 * Fix command-line mode and cleanup 'usage' after rename from log-collect.py
9 * Default text font size
10
11 3
12
13 * Read check permission
14
15 2
16
17 * Drop presence and network
18
19 1
20
21 * Initial Version
(New empty file)
0 <?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
1 <!ENTITY stroke_color "#010101">
2 <!ENTITY fill_color "#FFFFFF">
3 ]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="activity-log">
4
5 <rect fill="&fill_color;" height="32.442" stroke="&stroke_color;" stroke-linecap="round" stroke-width="3.5" width="43.457" x="5.646" y="9.404"/>
6 <circle cx="12.926" cy="16.867" fill="&stroke_color;" r="1.931" stroke="#000000" stroke-width="0.5"/>
7 <circle cx="12.926" cy="25.645" fill="&stroke_color;" r="1.931" stroke="#000000" stroke-width="0.5"/>
8 <circle cx="12.926" cy="34.424" fill="&stroke_color;" r="1.931" stroke="#000000" stroke-width="0.5"/>
9 <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="17.926" x2="33.926" y1="16.867" y2="16.867"/>
10 <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="17.926" x2="33.926" y1="25.625" y2="25.625"/>
11 <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="17.926" x2="33.926" y1="34.424" y2="34.424"/>
12 </g></svg>
0 [Activity]
1 name = Log Viewer
2 activity_version = 6
3 service_name = org.laptop.LogViewer
4 exec = sugar-activity logviewer.LogHandler -s
5 icon = activity-log
6
0 #!/usr/bin/env python
1 #
2 # Copyright (C) 2007, Pascal Scheffers <pascal@scheffers.net>
3 #
4 # Permission is hereby granted, free of charge, to any person
5 # obtaining a copy of this software and associated documentation
6 # files (the "Software"), to deal in the Software without
7 # restriction, including without limitation the rights to use,
8 # copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the
10 # Software is furnished to do so, subject to the following
11 # conditions:
12 #
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 # OTHER DEALINGS IN THE SOFTWARE.
24 #
25 # log-collect for OLPC
26 #
27 # Compile a report containing:
28 # * Basic system information:
29 # ** Serial number
30 # ** Battery type
31 # ** Build number
32 # ** Uptime
33 # ** disk free space
34 # ** ...
35 # * Installed packages list
36 # * All relevant log files (all of them, at first)
37 #
38 # The report is output as a tarfile
39 #
40 # This file has two modes:
41 # 1. It is a stand-alone python script, when invoked as 'log-collect'
42 # 2. It is a python module.
43
44 import os
45 import zipfile
46 import glob
47 import sys
48 import time
49
50 # The next couple are used by LogSend
51 import httplib
52 import mimetypes
53 import urlparse
54
55 class MachineProperties:
56 """Various machine properties in easy to access chunks.
57 """
58
59 def __read_file(self, filename):
60 """Read the entire contents of a file and return it as a string"""
61
62 data = ''
63
64 f = open(filename)
65 try:
66 data = f.read()
67 finally:
68 f.close()
69
70 return data
71
72 def olpc_build(self):
73 """Buildnumber, from /etc/issue"""
74 # Is there a better place to get the build number?
75 if not os.path.exists('/etc/issue'):
76 return '#/etc/issue not found'
77
78 # Needed, because we want to default to the first non blank line:
79 first_line = ''
80
81 for line in self.__read_file('/etc/issue').splitlines():
82 if line.lower().find('olpc build') > -1:
83 return line
84 if first_line == '':
85 first_line=line
86
87 return first_line
88
89 def uptime(self):
90 for line in self.__read_file('/proc/uptime').splitlines():
91 if line != '':
92 return line
93 return ''
94
95 def loadavg(self):
96 for line in self.__read_file('/proc/loadavg').splitlines():
97 if line != '':
98 return line
99 return ''
100
101 def kernel_version(self):
102 for line in self.__read_file('/proc/version').splitlines():
103 if line != '':
104 return line
105 return ''
106
107 def memfree(self):
108 line = ''
109
110 for line in self.__read_file('/proc/meminfo').splitlines():
111 if line.find('MemFree:') > -1:
112 return line[8:].strip()
113
114 def _mfg_data(self, item):
115 """Return mfg data item from /ofw/mfg-data/"""
116
117 if not os.path.exists('/ofw/mfg-data/'+item):
118 return ''
119
120 v = self.__read_file('/ofw/mfg-data/'+item)
121 # Remove trailing 0 character, if any:
122 if v != '' and ord(v[len(v)-1]) == 0:
123 v = v[:len(v)-1]
124
125 return v
126
127 def laptop_serial_number(self):
128 return self._mfg_data('SN')
129
130 def laptop_motherboard_number(self):
131 return self._mfg_data('B#')
132
133 def laptop_board_revision(self):
134 s = self._mfg_data('SG')[0:1]
135 if s == '':
136 return ''
137
138 return '%02X' % ord(self._mfg_data('SG')[0:1])
139
140
141 def laptop_uuid(self):
142 return self._mfg_data('U#')
143
144 def laptop_keyboard(self):
145 kb = self._mfg_data('KM') + '-'
146 kb += self._mfg_data('KL') + '-'
147 kb += self._mfg_data('KV')
148 return kb
149
150 def laptop_wireless_mac(self):
151 return self._mfg_data('WM')
152
153 def laptop_bios_version(self):
154 return self._mfg_data('BV')
155
156 def laptop_country(self):
157 return self._mfg_data('LA')
158
159 def laptop_localization(self):
160 return self._mfg_data('LO')
161
162 def _battery_info(self, item):
163 """ from /sys/class/power-supply/olpc-battery/ """
164 root = '/sys/class/power_supply/olpc-battery/'
165 if not os.path.exists(root+item):
166 return ''
167
168 return self.__read_file(root+item).strip()
169
170 def battery_serial_number(self):
171 return self._battery_info('serial_number')
172
173 def battery_capacity(self):
174 return self._battery_info('capacity') + ' ' + \
175 self._battery_info('capacity_level')
176
177 def battery_info(self):
178 #Should be just:
179 #return self._battery_info('uevent')
180
181 #But because of a bug in the kernel, that has trash, lets filter:
182 bi = ''
183 for line in self._battery_info('uevent').splitlines():
184 if line.startswith('POWER_'):
185 bi += line + '\n'
186
187 return bi
188
189 def disksize(self, path):
190 return os.statvfs(path).f_bsize * os.statvfs(path).f_blocks
191
192 def diskfree(self, path):
193 return os.statvfs(path).f_bsize * os.statvfs(path).f_bavail
194
195 def _read_popen(self, cmd):
196 p = os.popen(cmd)
197 s = ''
198 try:
199 for line in p:
200 s += line
201 finally:
202 p.close()
203
204 return s
205
206 def ifconfig(self):
207 return self._read_popen('/sbin/ifconfig')
208
209 def route_n(self):
210 return self._read_popen('/sbin/route -n')
211
212 def df_a(self):
213 return self._read_popen('/bin/df -a')
214
215 def ps_auxfwww(self):
216 return self._read_popen('/bin/ps auxfwww')
217
218 def usr_bin_free(self):
219 return self._read_popen('/usr/bin/free')
220
221 def top(self):
222 return self._read_popen('/usr/bin/top -bn2')
223
224 def installed_activities(self):
225 s = ''
226 for path in glob.glob('/usr/share/activities/*.activity'):
227 s += os.path.basename(path) + '\n'
228
229 for path in glob.glob('/home/olpc/Activities/*'):
230 s += '~' + os.path.basename(path) + '\n'
231
232 return s
233
234
235
236 class LogCollect:
237 """Collect XO logfiles and machine metadata for reporting to OLPC
238
239 """
240 def __init__(self):
241 self._mp = MachineProperties()
242
243 def write_logs(self, archive='', logbytes=15360):
244 """Write a zipfile containing the tails of the logfiles and machine info of the XO
245
246 Arguments:
247 archive - Specifies the location where to store the data
248 defaults to /dev/shm/logs-<xo-serial>.zip
249
250 logbytes - Maximum number of bytes to read from each log file.
251 0 means complete logfiles, not just the tail
252 -1 means only save machine info, no logs
253 """
254 #This function is crammed with try...except to make sure we get as much
255 #data as possible, if anything fails.
256
257 if archive=='':
258 archive = '/dev/shm/logs.zip'
259 try:
260 #With serial number is more convenient, but might fail for some
261 #Unknown reason...
262 archive = '/dev/shm/logs-%s.zip' % self._mp.laptop_serial_number()
263 except Exception:
264 pass
265
266 z = zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED)
267
268 try:
269 try:
270 z.writestr('info.txt', self.laptop_info())
271 except Exception, e:
272 z.writestr('info.txt',
273 "logcollect: could not add info.txt: %s" % e)
274
275 if logbytes > -1:
276 # Include some log files from /var/log.
277 for fn in ['dmesg', 'messages', 'cron', 'maillog','rpmpkgs',
278 'Xorg.0.log', 'spooler']:
279 try:
280 if os.access('/var/log/'+fn, os.F_OK):
281 if logbytes == 0:
282 z.write('/var/log/'+fn, 'var-log/'+fn)
283 else:
284 z.writestr('var-log/'+fn,
285 self.file_tail('/var/log/'+fn, logbytes))
286 except Exception, e:
287 z.writestr('var-log/'+fn,
288 "logcollect: could not add %s: %s" % (fn, e))
289
290 # Include all current ones from sugar/logs
291 for path in glob.glob('/home/olpc/.sugar/default/logs/*.log'):
292 try:
293 if os.access(path, os.F_OK):
294 if logbytes == 0:
295 z.write(path, 'sugar-logs/'+os.path.basename(path))
296 else:
297 z.writestr('sugar-logs/'+os.path.basename(path),
298 self.file_tail(path, logbytes))
299 except Exception, e:
300 z.writestr('sugar-logs/'+fn,
301 "logcollect: could not add %s: %s" % (fn, e))
302 try:
303 z.write('/etc/resolv.conf')
304 except Exception, e:
305 z.writestr('/etc/resolv.conf',
306 "logcollect: could not add resolv.conf: %s" % e)
307
308 except Exception, e:
309 print 'While creating zip archive: %s' % e
310
311 z.close()
312
313 return archive
314
315 def file_tail(self, filename, tailbytes):
316 """Read the tail (end) of the file
317
318 Arguments:
319 filename The name of the file to read
320 tailbytes Number of bytes to include or 0 for entire file
321 """
322
323 data = ''
324
325 f = open(filename)
326 try:
327 fsize = os.stat(filename).st_size
328
329 if tailbytes > 0 and fsize > tailbytes:
330 f.seek(-tailbytes, 2)
331
332 data = f.read()
333 finally:
334 f.close()
335
336 return data
337
338
339 def make_report(self, target='stdout'):
340 """Create the report
341
342 Arguments:
343 target - where to save the logs, a path or stdout
344
345 """
346
347 li = self.laptop_info()
348 for k, v in li.iteritems():
349 print k + ': ' +v
350
351 print self._mp.battery_info()
352
353 def laptop_info(self):
354 """Return a string with laptop serial, battery type, build, memory info, etc."""
355
356 s = ''
357 try:
358 # Do not include UUID!
359 s += 'laptop-info-version: 1.0\n'
360 s += 'clock: %f\n' % time.clock()
361 s += 'date: %s' % time.strftime("%a, %d %b %Y %H:%M:%S +0000",
362 time.gmtime())
363 s += 'memfree: %s\n' % self._mp.memfree()
364 s += 'disksize: %s MB\n' % ( self._mp.disksize('/') / (1024*1024) )
365 s += 'diskfree: %s MB\n' % ( self._mp.diskfree('/') / (1024*1024) )
366 s += 'olpc_build: %s\n' % self._mp.olpc_build()
367 s += 'kernel_version: %s\n' % self._mp.kernel_version()
368 s += 'uptime: %s\n' % self._mp.uptime()
369 s += 'loadavg: %s\n' % self._mp.loadavg()
370 s += 'serial-number: %s\n' % self._mp.laptop_serial_number()
371 s += 'motherboard-number: %s\n' % self._mp.laptop_motherboard_number()
372 s += 'board-revision: %s\n' % self._mp.laptop_board_revision()
373 s += 'keyboard: %s\n' % self._mp.laptop_keyboard()
374 s += 'wireless_mac: %s\n' % self._mp.laptop_wireless_mac()
375 s += 'firmware: %s\n' % self._mp.laptop_bios_version()
376 s += 'country: %s\n' % self._mp.laptop_country()
377 s += 'localization: %s\n' % self._mp.laptop_localization()
378
379 s += self._mp.battery_info()
380
381 s += "\n[/sbin/ifconfig]\n%s\n" % self._mp.ifconfig()
382 s += "\n[/sbin/route -n]\n%s\n" % self._mp.route_n()
383
384 s += '\n[Installed Activities]\n%s\n' % self._mp.installed_activities()
385
386 s += '\n[df -a]\n%s\n' % self._mp.df_a()
387 s += '\n[ps auxwww]\n%s\n' % self._mp.ps_auxfwww()
388 s += '\n[free]\n%s\n' % self._mp.usr_bin_free()
389 s += '\n[top -bn2]\n%s\n' % self._mp.top()
390 except Exception, e:
391 s += '\nException while building info:\n%s\n' % e
392
393 return s
394
395 class LogSend:
396
397 # post_multipart and encode_multipart_formdata have been taken from
398 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
399 def post_multipart(self, host, selector, fields, files):
400 """
401 Post fields and files to an http host as multipart/form-data.
402 fields is a sequence of (name, value) elements for regular form fields.
403 files is a sequence of (name, filename, value) elements for data to be uploaded as files
404 Return the server's response page.
405 """
406 content_type, body = self.encode_multipart_formdata(fields, files)
407 h = httplib.HTTP(host)
408 h.putrequest('POST', selector)
409 h.putheader('content-type', content_type)
410 h.putheader('content-length', str(len(body)))
411 h.putheader('Host', host)
412 h.endheaders()
413 h.send(body)
414 errcode, errmsg, headers = h.getreply()
415 return h.file.read()
416
417 def encode_multipart_formdata(self, fields, files):
418 """
419 fields is a sequence of (name, value) elements for regular form fields.
420 files is a sequence of (name, filename, value) elements for data to be uploaded as files
421 Return (content_type, body) ready for httplib.HTTP instance
422 """
423 BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
424 CRLF = '\r\n'
425 L = []
426 for (key, value) in fields:
427 L.append('--' + BOUNDARY)
428 L.append('Content-Disposition: form-data; name="%s"' % key)
429 L.append('')
430 L.append(value)
431 for (key, filename, value) in files:
432 L.append('--' + BOUNDARY)
433 L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
434 L.append('Content-Type: %s' % self.get_content_type(filename))
435 L.append('')
436 L.append(value)
437 L.append('--' + BOUNDARY + '--')
438 L.append('')
439 body = CRLF.join(L)
440 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
441 return content_type, body
442
443 def read_file(self, filename):
444 """Read the entire contents of a file and return it as a string"""
445
446 data = ''
447
448 f = open(filename)
449 try:
450 data = f.read()
451 finally:
452 f.close()
453
454 return data
455
456 def get_content_type(self, filename):
457 return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
458
459 def http_post_logs(self, url, archive):
460 #host, selector, fields, files
461 files = ('logs', os.path.basename(archive), self.read_file(archive)),
462
463 # Client= olpc will make the server return just "OK" or "FAIL"
464 fields = ('client', 'xo'),
465 urlparts = urlparse.urlsplit(url)
466 print "Sending logs to %s" % url
467 r = self.post_multipart(urlparts[1], urlparts[2], fields, files)
468 print r
469 return (r == 'OK')
470
471
472 # This script is dual-mode, it can be used as a command line tool and as
473 # a library.
474 if sys.argv[0].endswith('logcollect.py') or \
475 sys.argv[0].endswith('logcollect'):
476 print 'log-collect utility 1.0'
477
478 lc = LogCollect()
479 ls = LogSend()
480
481 logs = ''
482 mode = 'http'
483
484 if len(sys.argv)==1:
485 print """logcollect.py - send your XO logs to OLPC
486
487 Usage:
488 logcollect.py http - send logs to default server
489
490 logcollect.py http://server.name/submit.php
491 - submit logs to alternative server
492
493 logcollect.py file:/media/xxxx-yyyy/mylog.zip
494 - save the zip file on a USB device or SD card
495
496 logcollect.py all file:/media/xxxx-yyyy/mylog.zip
497 - Save to zip file and include ALL logs
498
499 logcollect.py none http
500 - Just send info.txt, but no logs via http.
501
502 logcollect.py none file
503 - Just save info.txt in /dev/shm/logs-SN123.zip
504
505 If you specify 'all' or 'none' you must specify http or file as well.
506 """
507 sys.exit()
508
509
510 logbytes = 15360
511 if len(sys.argv)>1:
512 mode = sys.argv[len(sys.argv)-1]
513 if sys.argv[1] == 'all':
514 logbytes = 0
515 if sys.argv[1] == 'none':
516 logbytes = -1
517
518
519 if mode.startswith('file'):
520 # file://
521 logs = mode[5:]
522
523 #if mode.lower().startswith('http'):
524 # pass
525 #else if mode.lower().startswith('usb'):
526 # pass
527 #else if mode.lower().startswith('sd'):
528 # pass
529
530 logs = lc.write_logs(logs, logbytes)
531 print 'Logs saved in %s' % logs
532
533 sent_ok = False
534 if len(sys.argv)>1:
535 mode = sys.argv[len(sys.argv)-1]
536
537 if mode.startswith('http'):
538 print "Trying to send the logs using HTTP (web)"
539 if len(mode) == 4:
540 url = 'http://olpc.scheffers.net/olpc/submit.tcl'
541 else:
542 url = mode
543
544 if ls.http_post_logs(url, logs):
545 print "Logs were sent."
546 sent_ok = True
547 else:
548 print "FAILED to send logs."
549
550
551 if sent_ok:
552 os.remove(logs)
553 print "Logs were sent, tempfile deleted."
554
555
0 #!/usr/bin/env python
1
2 # Copyright (C) 2006-2007, Eduardo Silva <edsiper@gmail.com>
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
18 import os
19 import logging
20 from gettext import gettext as _
21
22 import gtk
23 import dbus
24 import pygtk
25 import gobject
26 import pango
27 import gnomevfs
28
29 from sugar.activity import activity
30 from sugar import env
31 from sugar.graphics.toolbutton import ToolButton
32 from sugar.graphics.palette import Palette
33 from logcollect import LogCollect, LogSend
34
35 class MultiLogView(gtk.VBox):
36 def __init__(self, path, extra_files):
37 self._logs_path = path
38 self._active_log = None
39 self._extra_files = extra_files
40
41 # Creating Main treeview with Actitivities list
42 self._tv_menu = gtk.TreeView()
43 self._tv_menu.connect('cursor-changed', self._load_log)
44 self._tv_menu.set_rules_hint(True)
45
46 # Set width
47 box_width = gtk.gdk.screen_width() * 80 / 100
48 self._tv_menu.set_size_request(box_width*25/100, 0)
49
50 self._store_menu = gtk.TreeStore(str)
51 self._tv_menu.set_model(self._store_menu)
52
53 self._add_column(self._tv_menu, 'Sugar logs', 0)
54 self._logs = {}
55
56 # Activities menu
57 self.hbox = gtk.HBox(False, 3)
58 self.hbox.pack_start(self._tv_menu, True, True, 0)
59
60 # Activity log, set width
61 self._view = LogView()
62 self._view.set_size_request(box_width*75/100, 0)
63
64 self.hbox.pack_start(self._view, True, True, 0)
65 self.hbox.show_all()
66 self._configure_watcher()
67 self._create_log_view()
68
69
70 def _configure_watcher(self):
71 # Setting where gnomeVFS will be watching
72 gnomevfs.monitor_add('file://' + self._logs_path,
73 gnomevfs.MONITOR_DIRECTORY,
74 self._log_file_changed_cb)
75
76 for f in self._extra_files:
77 gnomevfs.monitor_add('file://' + f,
78 gnomevfs.MONITOR_FILE,
79 self._log_file_changed_cb)
80
81 def _log_file_changed_cb(self, monitor_uri, info_uri, event):
82 path = info_uri.split('file://')[-1]
83 filename = self._get_filename_from_path(path)
84
85 if event == gnomevfs.MONITOR_EVENT_CHANGED:
86 self._logs[filename].update()
87 elif event == gnomevfs.MONITOR_EVENT_DELETED:
88 self._delete_log_file_view(filename)
89 elif event == gnomevfs.MONITOR_EVENT_CREATED:
90 self._add_log_file(path)
91
92 # Load the log information in View (textview)
93 def _load_log(self, treeview):
94 treeselection = treeview.get_selection()
95 treestore, iter = treeselection.get_selected()
96
97 # Get current selection
98 act_log = self._store_menu.get_value(iter, 0)
99
100 # Set buffer and scroll down
101 self._view.textview.set_buffer(self._logs[act_log])
102 self._view.textview.scroll_to_mark(self._logs[act_log].get_insert(), 0)
103 self._active_log = act_log
104
105 def _create_log_view(self):
106 # Searching log files
107 for logfile in os.listdir(self._logs_path):
108 full_log_path = os.path.join(self._logs_path, logfile)
109 self._add_log_file(full_log_path)
110
111 for ext in self._extra_files:
112 self._add_log_file(ext)
113
114 return True
115
116 def _delete_log_file_view(self, logkey):
117 self._store_menu.remove(self._logs[logkey].iter)
118 del self._logs[logkey]
119
120 def _get_filename_from_path(self, path):
121 return path.split('/')[-1]
122
123 def _add_log_file(self, path):
124 if os.path.isdir(path):
125 return False
126
127 if not os.path.exists(path):
128 print "ERROR: %s don't exists" % path
129 return False
130
131 if not os.access(path, os.R_OK):
132 print "ERROR: I can't read '%s' file" % path
133 return False
134
135 logfile = self._get_filename_from_path(path)
136
137 if not self._logs.has_key(logfile):
138 iter = self._add_log_row(logfile)
139 model = LogBuffer(path, iter)
140 self._logs[logfile] = model
141
142 self._logs[logfile].update()
143 written = self._logs[logfile]._written
144
145 # Load the first iter
146 if self._active_log == None:
147 self._active_log = logfile
148 iter = self._tv_menu.get_model().get_iter_root()
149 self._tv_menu.get_selection().select_iter(iter)
150 self._load_log(self._tv_menu)
151
152 if written > 0 and self._active_log == logfile:
153 self._view.textview.scroll_to_mark(self._logs[logfile].get_insert(), 0)
154
155
156 def _add_log_row(self, name):
157 return self._insert_row(self._store_menu, None, name)
158
159 # Add a new column to the main treeview, (code from Memphis)
160 def _add_column(self, treeview, column_name, index):
161 cell = gtk.CellRendererText()
162 col_tv = gtk.TreeViewColumn(column_name, cell, text=index)
163 col_tv.set_resizable(True)
164 col_tv.set_property('clickable', True)
165
166 treeview.append_column(col_tv)
167
168 # Set the last column index added
169 self.last_col_index = index
170
171 # Insert a Row in our TreeView
172 def _insert_row(self, store, parent, name):
173 iter = store.insert_before(parent, None)
174 index = 0
175 store.set_value(iter, index , name)
176
177 return iter
178
179 class LogBuffer(gtk.TextBuffer):
180 def __init__(self, logfile, iter=None):
181 gtk.TextBuffer.__init__(self)
182
183 self._logfile = logfile
184 self._pos = 0
185 self.iter = iter
186 self.update()
187
188 def update(self):
189 try:
190 f = open(self._logfile, 'r')
191 init_pos = self._pos
192
193 f.seek(self._pos)
194 self.insert(self.get_end_iter(), f.read())
195 self._pos = f.tell()
196 f.close()
197
198 self._written = (self._pos - init_pos)
199 except:
200 self.insert(self.get_end_iter(), "Console error: can't open the file\n")
201 self._written = 0
202
203 class LogView(gtk.ScrolledWindow):
204 def __init__(self):
205 gtk.ScrolledWindow.__init__(self)
206
207 self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
208
209 self.textview = gtk.TextView()
210 self.textview.set_wrap_mode(gtk.WRAP_WORD)
211
212 # Set background color
213 bgcolor = gtk.gdk.color_parse("#FFFFFF")
214 self.textview.modify_base(gtk.STATE_NORMAL, bgcolor)
215
216 self.textview.set_editable(False)
217
218 self.add(self.textview)
219 self.textview.show()
220
221
222 class LogHandler(activity.Activity):
223 def __init__(self, handle):
224 activity.Activity.__init__(self, handle)
225 logging.debug('Starting the Log Viewer activity')
226 self.set_title(_('Log Viewer Activity'))
227
228 # Main path to watch: ~/.sugar/someuser/logs...
229 main_path = os.path.join(env.get_profile_path(), 'logs')
230
231 # extra files to watch in logviewer
232 ext_files = []
233 ext_files.append("/var/log/Xorg.0.log")
234 ext_files.append("/var/log/syslog")
235 ext_files.append("/var/log/messages")
236
237 self._viewer = MultiLogView(main_path, ext_files).hbox
238
239 self._box = gtk.HBox()
240 self._box.pack_start(self._viewer)
241 self._box.show()
242
243 self.set_canvas(self._box)
244
245 # TOOLBAR
246 toolbox = activity.ActivityToolbox(self)
247 toolbox.show()
248
249 toolbar = LogToolbar(self)
250 toolbox.add_toolbar(_('Tools'), toolbar)
251 toolbar.show()
252
253 self.set_toolbox(toolbox)
254 self.show()
255
256 # Dirty hide()
257 toolbar = toolbox.get_activity_toolbar()
258 toolbar.share.hide()
259 toolbar.keep.hide()
260
261 # Keeping this method to add new funcs later
262 def switch_to_logviewer(self):
263 self._clean_box()
264 self._box.pack_start(self._viewer)
265
266 class LogToolbar(gtk.Toolbar):
267 def __init__(self, handler):
268 gtk.Toolbar.__init__(self)
269 self._handler = handler
270
271 collector_palette = CollectorMenu()
272 logviewer = ToolButton('zoom-best-fit')
273 logviewer.set_palette(collector_palette)
274 logviewer.connect('clicked', self._on_logviewer_clicked_cb)
275 self.insert(logviewer, -1)
276 logviewer.show()
277
278 def _on_logviewer_clicked_cb(self, widget):
279 self._handler.switch_to_logviewer()
280
281 class CollectorMenu(Palette):
282 _DEFAULT_SERVER = 'http://olpc.scheffers.net/olpc/submit.tcl'
283
284 def __init__(self):
285 Palette.__init__(self, 'Log Collector: send XO information')
286
287 self._collector = LogCollect()
288 label = gtk.Label(_('Log collector allow to send information about\n\
289 the system and running process to a central\nserver, use this option if you \
290 want to report\nsome detected problem'))
291
292 send_button = gtk.Button(_('Send information'))
293 send_button.connect('clicked', self._on_send_button_clicked_cb)
294
295 vbox = gtk.VBox(False, 5)
296 vbox.pack_start(label)
297 vbox.pack_start(send_button)
298 vbox.show_all()
299
300 self.set_content(vbox)
301
302 def _on_send_button_clicked_cb(self, button):
303 # Using the default values, just for testing...
304 data = self._collector.write_logs()
305 sender = LogSend()
306
307 if sender.http_post_logs(self._DEFAULT_SERVER, data):
308 print "Logs sent...OK"
309 else:
310 print "FAILED to send logs"
311
312 os.remove(data)
313 self.popdown()
0 #!/usr/bin/python
1
2 # Copyright (C) 2006, Red Hat, Inc.
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
18 from sugar.activity import bundlebuilder
19
20 bundlebuilder.start('Log')
21