diff --git a/Log.activity/NEWS b/Log.activity/NEWS new file mode 100644 index 0000000..b7cc6e5 --- /dev/null +++ b/Log.activity/NEWS @@ -0,0 +1,22 @@ +5 + +* Harden logcollect.py with a whole bunch of try...except to always collect as + much as possible. (pascal) + +4 + +* logcollect.py now defaults to http://olpc.scheffers.net/olpc/submit.tcl +* Fix command-line mode and cleanup 'usage' after rename from log-collect.py +* Default text font size + +3 + +* Read check permission + +2 + +* Drop presence and network + +1 + +* Initial Version diff --git a/Log.activity/README b/Log.activity/README new file mode 100644 index 0000000..e69de29 diff --git a/Log.activity/activity/activity-log.svg b/Log.activity/activity/activity-log.svg new file mode 100644 index 0000000..d018bcb --- /dev/null +++ b/Log.activity/activity/activity-log.svg @@ -0,0 +1,13 @@ + + +]> + + + + + + + + + \ No newline at end of file diff --git a/Log.activity/activity/activity.info b/Log.activity/activity/activity.info new file mode 100644 index 0000000..51ef1eb --- /dev/null +++ b/Log.activity/activity/activity.info @@ -0,0 +1,7 @@ +[Activity] +name = Log Viewer +activity_version = 6 +service_name = org.laptop.LogViewer +exec = sugar-activity logviewer.LogHandler -s +icon = activity-log + diff --git a/Log.activity/logcollect.py b/Log.activity/logcollect.py new file mode 100644 index 0000000..706ba64 --- /dev/null +++ b/Log.activity/logcollect.py @@ -0,0 +1,556 @@ +#!/usr/bin/env python +# +# Copyright (C) 2007, Pascal Scheffers +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# log-collect for OLPC +# +# Compile a report containing: +# * Basic system information: +# ** Serial number +# ** Battery type +# ** Build number +# ** Uptime +# ** disk free space +# ** ... +# * Installed packages list +# * All relevant log files (all of them, at first) +# +# The report is output as a tarfile +# +# This file has two modes: +# 1. It is a stand-alone python script, when invoked as 'log-collect' +# 2. It is a python module. + +import os +import zipfile +import glob +import sys +import time + +# The next couple are used by LogSend +import httplib +import mimetypes +import urlparse + +class MachineProperties: + """Various machine properties in easy to access chunks. + """ + + def __read_file(self, filename): + """Read the entire contents of a file and return it as a string""" + + data = '' + + f = open(filename) + try: + data = f.read() + finally: + f.close() + + return data + + def olpc_build(self): + """Buildnumber, from /etc/issue""" + # Is there a better place to get the build number? + if not os.path.exists('/etc/issue'): + return '#/etc/issue not found' + + # Needed, because we want to default to the first non blank line: + first_line = '' + + for line in self.__read_file('/etc/issue').splitlines(): + if line.lower().find('olpc build') > -1: + return line + if first_line == '': + first_line=line + + return first_line + + def uptime(self): + for line in self.__read_file('/proc/uptime').splitlines(): + if line != '': + return line + return '' + + def loadavg(self): + for line in self.__read_file('/proc/loadavg').splitlines(): + if line != '': + return line + return '' + + def kernel_version(self): + for line in self.__read_file('/proc/version').splitlines(): + if line != '': + return line + return '' + + def memfree(self): + line = '' + + for line in self.__read_file('/proc/meminfo').splitlines(): + if line.find('MemFree:') > -1: + return line[8:].strip() + + def _mfg_data(self, item): + """Return mfg data item from /ofw/mfg-data/""" + + if not os.path.exists('/ofw/mfg-data/'+item): + return '' + + v = self.__read_file('/ofw/mfg-data/'+item) + # Remove trailing 0 character, if any: + if v != '' and ord(v[len(v)-1]) == 0: + v = v[:len(v)-1] + + return v + + def laptop_serial_number(self): + return self._mfg_data('SN') + + def laptop_motherboard_number(self): + return self._mfg_data('B#') + + def laptop_board_revision(self): + s = self._mfg_data('SG')[0:1] + if s == '': + return '' + + return '%02X' % ord(self._mfg_data('SG')[0:1]) + + + def laptop_uuid(self): + return self._mfg_data('U#') + + def laptop_keyboard(self): + kb = self._mfg_data('KM') + '-' + kb += self._mfg_data('KL') + '-' + kb += self._mfg_data('KV') + return kb + + def laptop_wireless_mac(self): + return self._mfg_data('WM') + + def laptop_bios_version(self): + return self._mfg_data('BV') + + def laptop_country(self): + return self._mfg_data('LA') + + def laptop_localization(self): + return self._mfg_data('LO') + + def _battery_info(self, item): + """ from /sys/class/power-supply/olpc-battery/ """ + root = '/sys/class/power_supply/olpc-battery/' + if not os.path.exists(root+item): + return '' + + return self.__read_file(root+item).strip() + + def battery_serial_number(self): + return self._battery_info('serial_number') + + def battery_capacity(self): + return self._battery_info('capacity') + ' ' + \ + self._battery_info('capacity_level') + + def battery_info(self): + #Should be just: + #return self._battery_info('uevent') + + #But because of a bug in the kernel, that has trash, lets filter: + bi = '' + for line in self._battery_info('uevent').splitlines(): + if line.startswith('POWER_'): + bi += line + '\n' + + return bi + + def disksize(self, path): + return os.statvfs(path).f_bsize * os.statvfs(path).f_blocks + + def diskfree(self, path): + return os.statvfs(path).f_bsize * os.statvfs(path).f_bavail + + def _read_popen(self, cmd): + p = os.popen(cmd) + s = '' + try: + for line in p: + s += line + finally: + p.close() + + return s + + def ifconfig(self): + return self._read_popen('/sbin/ifconfig') + + def route_n(self): + return self._read_popen('/sbin/route -n') + + def df_a(self): + return self._read_popen('/bin/df -a') + + def ps_auxfwww(self): + return self._read_popen('/bin/ps auxfwww') + + def usr_bin_free(self): + return self._read_popen('/usr/bin/free') + + def top(self): + return self._read_popen('/usr/bin/top -bn2') + + def installed_activities(self): + s = '' + for path in glob.glob('/usr/share/activities/*.activity'): + s += os.path.basename(path) + '\n' + + for path in glob.glob('/home/olpc/Activities/*'): + s += '~' + os.path.basename(path) + '\n' + + return s + + + +class LogCollect: + """Collect XO logfiles and machine metadata for reporting to OLPC + + """ + def __init__(self): + self._mp = MachineProperties() + + def write_logs(self, archive='', logbytes=15360): + """Write a zipfile containing the tails of the logfiles and machine info of the XO + + Arguments: + archive - Specifies the location where to store the data + defaults to /dev/shm/logs-.zip + + logbytes - Maximum number of bytes to read from each log file. + 0 means complete logfiles, not just the tail + -1 means only save machine info, no logs + """ + #This function is crammed with try...except to make sure we get as much + #data as possible, if anything fails. + + if archive=='': + archive = '/dev/shm/logs.zip' + try: + #With serial number is more convenient, but might fail for some + #Unknown reason... + archive = '/dev/shm/logs-%s.zip' % self._mp.laptop_serial_number() + except Exception: + pass + + z = zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED) + + try: + try: + z.writestr('info.txt', self.laptop_info()) + except Exception, e: + z.writestr('info.txt', + "logcollect: could not add info.txt: %s" % e) + + if logbytes > -1: + # Include some log files from /var/log. + for fn in ['dmesg', 'messages', 'cron', 'maillog','rpmpkgs', + 'Xorg.0.log', 'spooler']: + try: + if os.access('/var/log/'+fn, os.F_OK): + if logbytes == 0: + z.write('/var/log/'+fn, 'var-log/'+fn) + else: + z.writestr('var-log/'+fn, + self.file_tail('/var/log/'+fn, logbytes)) + except Exception, e: + z.writestr('var-log/'+fn, + "logcollect: could not add %s: %s" % (fn, e)) + + # Include all current ones from sugar/logs + for path in glob.glob('/home/olpc/.sugar/default/logs/*.log'): + try: + if os.access(path, os.F_OK): + if logbytes == 0: + z.write(path, 'sugar-logs/'+os.path.basename(path)) + else: + z.writestr('sugar-logs/'+os.path.basename(path), + self.file_tail(path, logbytes)) + except Exception, e: + z.writestr('sugar-logs/'+fn, + "logcollect: could not add %s: %s" % (fn, e)) + try: + z.write('/etc/resolv.conf') + except Exception, e: + z.writestr('/etc/resolv.conf', + "logcollect: could not add resolv.conf: %s" % e) + + except Exception, e: + print 'While creating zip archive: %s' % e + + z.close() + + return archive + + def file_tail(self, filename, tailbytes): + """Read the tail (end) of the file + + Arguments: + filename The name of the file to read + tailbytes Number of bytes to include or 0 for entire file + """ + + data = '' + + f = open(filename) + try: + fsize = os.stat(filename).st_size + + if tailbytes > 0 and fsize > tailbytes: + f.seek(-tailbytes, 2) + + data = f.read() + finally: + f.close() + + return data + + + def make_report(self, target='stdout'): + """Create the report + + Arguments: + target - where to save the logs, a path or stdout + + """ + + li = self.laptop_info() + for k, v in li.iteritems(): + print k + ': ' +v + + print self._mp.battery_info() + + def laptop_info(self): + """Return a string with laptop serial, battery type, build, memory info, etc.""" + + s = '' + try: + # Do not include UUID! + s += 'laptop-info-version: 1.0\n' + s += 'clock: %f\n' % time.clock() + s += 'date: %s' % time.strftime("%a, %d %b %Y %H:%M:%S +0000", + time.gmtime()) + s += 'memfree: %s\n' % self._mp.memfree() + s += 'disksize: %s MB\n' % ( self._mp.disksize('/') / (1024*1024) ) + s += 'diskfree: %s MB\n' % ( self._mp.diskfree('/') / (1024*1024) ) + s += 'olpc_build: %s\n' % self._mp.olpc_build() + s += 'kernel_version: %s\n' % self._mp.kernel_version() + s += 'uptime: %s\n' % self._mp.uptime() + s += 'loadavg: %s\n' % self._mp.loadavg() + s += 'serial-number: %s\n' % self._mp.laptop_serial_number() + s += 'motherboard-number: %s\n' % self._mp.laptop_motherboard_number() + s += 'board-revision: %s\n' % self._mp.laptop_board_revision() + s += 'keyboard: %s\n' % self._mp.laptop_keyboard() + s += 'wireless_mac: %s\n' % self._mp.laptop_wireless_mac() + s += 'firmware: %s\n' % self._mp.laptop_bios_version() + s += 'country: %s\n' % self._mp.laptop_country() + s += 'localization: %s\n' % self._mp.laptop_localization() + + s += self._mp.battery_info() + + s += "\n[/sbin/ifconfig]\n%s\n" % self._mp.ifconfig() + s += "\n[/sbin/route -n]\n%s\n" % self._mp.route_n() + + s += '\n[Installed Activities]\n%s\n' % self._mp.installed_activities() + + s += '\n[df -a]\n%s\n' % self._mp.df_a() + s += '\n[ps auxwww]\n%s\n' % self._mp.ps_auxfwww() + s += '\n[free]\n%s\n' % self._mp.usr_bin_free() + s += '\n[top -bn2]\n%s\n' % self._mp.top() + except Exception, e: + s += '\nException while building info:\n%s\n' % e + + return s + +class LogSend: + + # post_multipart and encode_multipart_formdata have been taken from + # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 + def post_multipart(self, host, selector, fields, files): + """ + Post fields and files to an http host as multipart/form-data. + fields is a sequence of (name, value) elements for regular form fields. + files is a sequence of (name, filename, value) elements for data to be uploaded as files + Return the server's response page. + """ + content_type, body = self.encode_multipart_formdata(fields, files) + h = httplib.HTTP(host) + h.putrequest('POST', selector) + h.putheader('content-type', content_type) + h.putheader('content-length', str(len(body))) + h.putheader('Host', host) + h.endheaders() + h.send(body) + errcode, errmsg, headers = h.getreply() + return h.file.read() + + def encode_multipart_formdata(self, fields, files): + """ + fields is a sequence of (name, value) elements for regular form fields. + files is a sequence of (name, filename, value) elements for data to be uploaded as files + Return (content_type, body) ready for httplib.HTTP instance + """ + BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$' + CRLF = '\r\n' + L = [] + for (key, value) in fields: + L.append('--' + BOUNDARY) + L.append('Content-Disposition: form-data; name="%s"' % key) + L.append('') + L.append(value) + for (key, filename, value) in files: + L.append('--' + BOUNDARY) + L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) + L.append('Content-Type: %s' % self.get_content_type(filename)) + L.append('') + L.append(value) + L.append('--' + BOUNDARY + '--') + L.append('') + body = CRLF.join(L) + content_type = 'multipart/form-data; boundary=%s' % BOUNDARY + return content_type, body + + def read_file(self, filename): + """Read the entire contents of a file and return it as a string""" + + data = '' + + f = open(filename) + try: + data = f.read() + finally: + f.close() + + return data + + def get_content_type(self, filename): + return mimetypes.guess_type(filename)[0] or 'application/octet-stream' + + def http_post_logs(self, url, archive): + #host, selector, fields, files + files = ('logs', os.path.basename(archive), self.read_file(archive)), + + # Client= olpc will make the server return just "OK" or "FAIL" + fields = ('client', 'xo'), + urlparts = urlparse.urlsplit(url) + print "Sending logs to %s" % url + r = self.post_multipart(urlparts[1], urlparts[2], fields, files) + print r + return (r == 'OK') + + +# This script is dual-mode, it can be used as a command line tool and as +# a library. +if sys.argv[0].endswith('logcollect.py') or \ + sys.argv[0].endswith('logcollect'): + print 'log-collect utility 1.0' + + lc = LogCollect() + ls = LogSend() + + logs = '' + mode = 'http' + + if len(sys.argv)==1: + print """logcollect.py - send your XO logs to OLPC + +Usage: + logcollect.py http - send logs to default server + + logcollect.py http://server.name/submit.php + - submit logs to alternative server + + logcollect.py file:/media/xxxx-yyyy/mylog.zip + - save the zip file on a USB device or SD card + + logcollect.py all file:/media/xxxx-yyyy/mylog.zip + - Save to zip file and include ALL logs + + logcollect.py none http + - Just send info.txt, but no logs via http. + + logcollect.py none file + - Just save info.txt in /dev/shm/logs-SN123.zip + + If you specify 'all' or 'none' you must specify http or file as well. + """ + sys.exit() + + + logbytes = 15360 + if len(sys.argv)>1: + mode = sys.argv[len(sys.argv)-1] + if sys.argv[1] == 'all': + logbytes = 0 + if sys.argv[1] == 'none': + logbytes = -1 + + + if mode.startswith('file'): + # file:// + logs = mode[5:] + + #if mode.lower().startswith('http'): + # pass + #else if mode.lower().startswith('usb'): + # pass + #else if mode.lower().startswith('sd'): + # pass + + logs = lc.write_logs(logs, logbytes) + print 'Logs saved in %s' % logs + + sent_ok = False + if len(sys.argv)>1: + mode = sys.argv[len(sys.argv)-1] + + if mode.startswith('http'): + print "Trying to send the logs using HTTP (web)" + if len(mode) == 4: + url = 'http://olpc.scheffers.net/olpc/submit.tcl' + else: + url = mode + + if ls.http_post_logs(url, logs): + print "Logs were sent." + sent_ok = True + else: + print "FAILED to send logs." + + + if sent_ok: + os.remove(logs) + print "Logs were sent, tempfile deleted." + + diff --git a/Log.activity/logviewer.py b/Log.activity/logviewer.py new file mode 100644 index 0000000..ed5eb1d --- /dev/null +++ b/Log.activity/logviewer.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python + +# Copyright (C) 2006-2007, Eduardo Silva +# +# 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 os +import logging +from gettext import gettext as _ + +import gtk +import dbus +import pygtk +import gobject +import pango +import gnomevfs + +from sugar.activity import activity +from sugar import env +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.palette import Palette +from logcollect import LogCollect, LogSend + +class MultiLogView(gtk.VBox): + def __init__(self, path, extra_files): + self._logs_path = path + self._active_log = None + self._extra_files = extra_files + + # Creating Main treeview with Actitivities list + self._tv_menu = gtk.TreeView() + self._tv_menu.connect('cursor-changed', self._load_log) + self._tv_menu.set_rules_hint(True) + + # Set width + box_width = gtk.gdk.screen_width() * 80 / 100 + self._tv_menu.set_size_request(box_width*25/100, 0) + + self._store_menu = gtk.TreeStore(str) + self._tv_menu.set_model(self._store_menu) + + self._add_column(self._tv_menu, 'Sugar logs', 0) + self._logs = {} + + # Activities menu + self.hbox = gtk.HBox(False, 3) + self.hbox.pack_start(self._tv_menu, True, True, 0) + + # Activity log, set width + self._view = LogView() + self._view.set_size_request(box_width*75/100, 0) + + self.hbox.pack_start(self._view, True, True, 0) + self.hbox.show_all() + self._configure_watcher() + self._create_log_view() + + + def _configure_watcher(self): + # Setting where gnomeVFS will be watching + gnomevfs.monitor_add('file://' + self._logs_path, + gnomevfs.MONITOR_DIRECTORY, + self._log_file_changed_cb) + + for f in self._extra_files: + gnomevfs.monitor_add('file://' + f, + gnomevfs.MONITOR_FILE, + self._log_file_changed_cb) + + def _log_file_changed_cb(self, monitor_uri, info_uri, event): + path = info_uri.split('file://')[-1] + filename = self._get_filename_from_path(path) + + if event == gnomevfs.MONITOR_EVENT_CHANGED: + self._logs[filename].update() + elif event == gnomevfs.MONITOR_EVENT_DELETED: + self._delete_log_file_view(filename) + elif event == gnomevfs.MONITOR_EVENT_CREATED: + self._add_log_file(path) + + # Load the log information in View (textview) + def _load_log(self, treeview): + treeselection = treeview.get_selection() + treestore, iter = treeselection.get_selected() + + # Get current selection + act_log = self._store_menu.get_value(iter, 0) + + # Set buffer and scroll down + self._view.textview.set_buffer(self._logs[act_log]) + self._view.textview.scroll_to_mark(self._logs[act_log].get_insert(), 0) + self._active_log = act_log + + def _create_log_view(self): + # Searching log files + for logfile in os.listdir(self._logs_path): + full_log_path = os.path.join(self._logs_path, logfile) + self._add_log_file(full_log_path) + + for ext in self._extra_files: + self._add_log_file(ext) + + return True + + def _delete_log_file_view(self, logkey): + self._store_menu.remove(self._logs[logkey].iter) + del self._logs[logkey] + + def _get_filename_from_path(self, path): + return path.split('/')[-1] + + def _add_log_file(self, path): + if os.path.isdir(path): + return False + + if not os.path.exists(path): + print "ERROR: %s don't exists" % path + return False + + if not os.access(path, os.R_OK): + print "ERROR: I can't read '%s' file" % path + return False + + logfile = self._get_filename_from_path(path) + + if not self._logs.has_key(logfile): + iter = self._add_log_row(logfile) + model = LogBuffer(path, iter) + self._logs[logfile] = model + + self._logs[logfile].update() + written = self._logs[logfile]._written + + # Load the first iter + if self._active_log == None: + self._active_log = logfile + iter = self._tv_menu.get_model().get_iter_root() + self._tv_menu.get_selection().select_iter(iter) + self._load_log(self._tv_menu) + + if written > 0 and self._active_log == logfile: + self._view.textview.scroll_to_mark(self._logs[logfile].get_insert(), 0) + + + def _add_log_row(self, name): + return self._insert_row(self._store_menu, None, name) + + # Add a new column to the main treeview, (code from Memphis) + def _add_column(self, treeview, column_name, index): + cell = gtk.CellRendererText() + col_tv = gtk.TreeViewColumn(column_name, cell, text=index) + col_tv.set_resizable(True) + col_tv.set_property('clickable', True) + + treeview.append_column(col_tv) + + # Set the last column index added + self.last_col_index = index + + # Insert a Row in our TreeView + def _insert_row(self, store, parent, name): + iter = store.insert_before(parent, None) + index = 0 + store.set_value(iter, index , name) + + return iter + +class LogBuffer(gtk.TextBuffer): + def __init__(self, logfile, iter=None): + gtk.TextBuffer.__init__(self) + + self._logfile = logfile + self._pos = 0 + self.iter = iter + self.update() + + def update(self): + try: + f = open(self._logfile, 'r') + init_pos = self._pos + + f.seek(self._pos) + self.insert(self.get_end_iter(), f.read()) + self._pos = f.tell() + f.close() + + self._written = (self._pos - init_pos) + except: + self.insert(self.get_end_iter(), "Console error: can't open the file\n") + self._written = 0 + +class LogView(gtk.ScrolledWindow): + def __init__(self): + gtk.ScrolledWindow.__init__(self) + + self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + + self.textview = gtk.TextView() + self.textview.set_wrap_mode(gtk.WRAP_WORD) + + # Set background color + bgcolor = gtk.gdk.color_parse("#FFFFFF") + self.textview.modify_base(gtk.STATE_NORMAL, bgcolor) + + self.textview.set_editable(False) + + self.add(self.textview) + self.textview.show() + + +class LogHandler(activity.Activity): + def __init__(self, handle): + activity.Activity.__init__(self, handle) + logging.debug('Starting the Log Viewer activity') + self.set_title(_('Log Viewer Activity')) + + # Main path to watch: ~/.sugar/someuser/logs... + main_path = os.path.join(env.get_profile_path(), 'logs') + + # extra files to watch in logviewer + ext_files = [] + ext_files.append("/var/log/Xorg.0.log") + ext_files.append("/var/log/syslog") + ext_files.append("/var/log/messages") + + self._viewer = MultiLogView(main_path, ext_files).hbox + + self._box = gtk.HBox() + self._box.pack_start(self._viewer) + self._box.show() + + self.set_canvas(self._box) + + # TOOLBAR + toolbox = activity.ActivityToolbox(self) + toolbox.show() + + toolbar = LogToolbar(self) + toolbox.add_toolbar(_('Tools'), toolbar) + toolbar.show() + + self.set_toolbox(toolbox) + self.show() + + # Dirty hide() + toolbar = toolbox.get_activity_toolbar() + toolbar.share.hide() + toolbar.keep.hide() + + # Keeping this method to add new funcs later + def switch_to_logviewer(self): + self._clean_box() + self._box.pack_start(self._viewer) + +class LogToolbar(gtk.Toolbar): + def __init__(self, handler): + gtk.Toolbar.__init__(self) + self._handler = handler + + collector_palette = CollectorMenu() + logviewer = ToolButton('zoom-best-fit') + logviewer.set_palette(collector_palette) + logviewer.connect('clicked', self._on_logviewer_clicked_cb) + self.insert(logviewer, -1) + logviewer.show() + + def _on_logviewer_clicked_cb(self, widget): + self._handler.switch_to_logviewer() + +class CollectorMenu(Palette): + _DEFAULT_SERVER = 'http://olpc.scheffers.net/olpc/submit.tcl' + + def __init__(self): + Palette.__init__(self, 'Log Collector: send XO information') + + self._collector = LogCollect() + label = gtk.Label(_('Log collector allow to send information about\n\ +the system and running process to a central\nserver, use this option if you \ +want to report\nsome detected problem')) + + send_button = gtk.Button(_('Send information')) + send_button.connect('clicked', self._on_send_button_clicked_cb) + + vbox = gtk.VBox(False, 5) + vbox.pack_start(label) + vbox.pack_start(send_button) + vbox.show_all() + + self.set_content(vbox) + + def _on_send_button_clicked_cb(self, button): + # Using the default values, just for testing... + data = self._collector.write_logs() + sender = LogSend() + + if sender.http_post_logs(self._DEFAULT_SERVER, data): + print "Logs sent...OK" + else: + print "FAILED to send logs" + + os.remove(data) + self.popdown() diff --git a/Log.activity/setup.py b/Log.activity/setup.py new file mode 100644 index 0000000..96ebae3 --- /dev/null +++ b/Log.activity/setup.py @@ -0,0 +1,22 @@ +#!/usr/bin/python + +# Copyright (C) 2006, Red Hat, Inc. +# +# 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 sugar.activity import bundlebuilder + +bundlebuilder.start('Log') +