Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorgan Collett <morgan.collett@gmail.com>2009-01-20 13:15:24 (GMT)
committer Morgan Collett <morgan.collett@gmail.com>2009-01-20 13:15:24 (GMT)
commit83a0738fed3a3e13ef718193f8cdca0cc1a5b034 (patch)
treeb7100f7f708cd3560e30dc2c0b80e3aceb00c7c4
Add files to git
-rw-r--r--Log.activity/NEWS22
-rw-r--r--Log.activity/README0
-rw-r--r--Log.activity/activity/activity-log.svg13
-rw-r--r--Log.activity/activity/activity.info7
-rw-r--r--Log.activity/logcollect.py556
-rw-r--r--Log.activity/logviewer.py314
-rw-r--r--Log.activity/setup.py22
-rw-r--r--debian/changelog5
-rw-r--r--debian/compat1
-rw-r--r--debian/control14
-rw-r--r--debian/copyright57
-rw-r--r--debian/install1
-rwxr-xr-xdebian/rules4
13 files changed, 1016 insertions, 0 deletions
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
--- /dev/null
+++ b/Log.activity/README
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 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]><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">
+
+ <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"/>
+ <circle cx="12.926" cy="16.867" fill="&stroke_color;" r="1.931" stroke="#000000" stroke-width="0.5"/>
+ <circle cx="12.926" cy="25.645" fill="&stroke_color;" r="1.931" stroke="#000000" stroke-width="0.5"/>
+ <circle cx="12.926" cy="34.424" fill="&stroke_color;" r="1.931" stroke="#000000" stroke-width="0.5"/>
+ <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="17.926" x2="33.926" y1="16.867" y2="16.867"/>
+ <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="17.926" x2="33.926" y1="25.625" y2="25.625"/>
+ <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="17.926" x2="33.926" y1="34.424" y2="34.424"/>
+</g></svg> \ 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 <pascal@scheffers.net>
+#
+# 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-<xo-serial>.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 <edsiper@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 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')
+
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..63f015b
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+sugar-logviewer-activity (6-0ubuntu1) hardy; urgency=low
+
+ * Initial upload to Ubuntu
+
+ -- Jani Monoses <jani@ubuntu.com> Sat, 08 Dec 2007 21:51:00 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..7ed6ff8
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+5
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..d0c1d73
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,14 @@
+Source: sugar-logviewer-activity
+Section: x11
+Priority: optional
+Maintainer: Jani Monoses <jani@ubuntu.com>
+Build-Depends: cdbs, debhelper (>= 5)
+Standards-Version: 3.7.2
+
+Package: sugar-logviewer-activity
+Architecture: all
+Depends: sugar
+Description: log viewing activity on the XO laptop
+ Log viewing activity used in the Sugar interface on the XO laptop.
+ It shows system logs, as well as debugging information from running
+ activities and Sugar itself.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..9c160f1
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,57 @@
+This package was debianized by Jani Monoses <jani@ubuntu.com> on
+Mon, 10 Sep 2007 23:13:09 +0200.
+
+It was downloaded from git://dev.laptop.org/
+
+Upstream Authors:
+
+ Pascal Scheffers <pascal@dragon.scheffers.net>
+ Eduardo Silva <edsiper@gmail.com>
+
+Copyright: 2007, Red Hat
+ 2007, Pascal Scheffers <pascal@dragon.scheffers.net>
+ 2007, Eduardo Silva <edsiper@gmail.com>
+
+License:
+
+Parts of this software are licensed under this one
+
+ 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.
+
+Others are under GPL:
+
+ This package 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 package 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 package; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+On Debian systems, the complete text of the GNU General
+Public License can be found in `/usr/share/common-licenses/GPL'.
+
+The Debian packaging is (C) 2007, Jani Monoses <jani@ubuntu.com> and
+is licensed under the GPL, see above.
diff --git a/debian/install b/debian/install
new file mode 100644
index 0000000..c9a8135
--- /dev/null
+++ b/debian/install
@@ -0,0 +1 @@
+Log.activity /usr/share/activities
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..a345068
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,4 @@
+#!/usr/bin/make -f
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/rules/simple-patchsys.mk