Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/Log.activity/logviewer.py
diff options
context:
space:
mode:
Diffstat (limited to 'Log.activity/logviewer.py')
-rw-r--r--Log.activity/logviewer.py596
1 files changed, 410 insertions, 186 deletions
diff --git a/Log.activity/logviewer.py b/Log.activity/logviewer.py
index ed5eb1d..516656a 100644
--- a/Log.activity/logviewer.py
+++ b/Log.activity/logviewer.py
@@ -20,275 +20,483 @@ import os
import logging
from gettext import gettext as _
+import re
+
import gtk
-import dbus
-import pygtk
-import gobject
import pango
+import gobject
import gnomevfs
from sugar.activity import activity
from sugar import env
+from sugar.graphics import iconentry
from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toggletoolbutton import ToggleToolButton
from sugar.graphics.palette import Palette
+from sugar.graphics.alert import NotifyAlert
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
+# Should be builtin to sugar.graphics.alert.NotifyAlert...
+def _notify_response_cb(notify, response, activity):
+ activity.remove_alert(notify)
- # 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)
+class MultiLogView(gtk.HBox):
+ def __init__(self, paths, extra_files):
+ gtk.HBox.__init__(self, False, 3)
+
+ self.paths = paths
+ self.extra_files = extra_files
- # 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.active_log = None
+ self.logs = {}
+
+ self.search_text = ''
+
+ self._build_treeview()
+ self._build_textview()
+
+ self.show_all()
+
+ self._configure_watcher()
+ self._find_logs()
- self._add_column(self._tv_menu, 'Sugar logs', 0)
- self._logs = {}
+ def _build_treeview(self):
+ self._treeview = gtk.TreeView()
- # Activities menu
- self.hbox = gtk.HBox(False, 3)
- self.hbox.pack_start(self._tv_menu, True, True, 0)
+ self._treeview.set_rules_hint(True)
+ self._treeview.connect('cursor-changed', self._cursor_changed_cb)
- # Activity log, set width
- self._view = LogView()
- self._view.set_size_request(box_width*75/100, 0)
+ self._treemodel = gtk.TreeStore(gobject.TYPE_STRING)
- self.hbox.pack_start(self._view, True, True, 0)
- self.hbox.show_all()
- self._configure_watcher()
- self._create_log_view()
+ sorted = gtk.TreeModelSort(self._treemodel)
+ sorted.set_sort_column_id(0, gtk.SORT_ASCENDING)
+ sorted.set_sort_func(0, self._sort_logfile)
+ self._treeview.set_model(sorted)
+
+ renderer = gtk.CellRendererText()
+ col = gtk.TreeViewColumn(_('Log Files'), renderer, text=0)
+ self._treeview.append_column(col)
+ self.path_iter = {}
+ for p in self.paths:
+ self.path_iter[p] = self._treemodel.append(None, [p])
+
+ if len(self.extra_files):
+ self.extra_iter = self._treemodel.append(None, [_('Other')])
+
+ scroll = gtk.ScrolledWindow()
+ scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scroll.add(self._treeview)
+
+ scroll.set_size_request(gtk.gdk.screen_width()*30/100, 0)
+ self.pack_start(scroll, True, True, 0)
+
+ def _build_textview(self):
+ self._textview = gtk.TextView()
+ self._textview.set_wrap_mode(gtk.WRAP_NONE)
+
+ pangoFont = pango.FontDescription('Courier 8')
+ self._textview.modify_font(pangoFont)
+
+ bgcolor = gtk.gdk.color_parse('#FFFFFF')
+ self._textview.modify_base(gtk.STATE_NORMAL, bgcolor)
+
+ self._textview.set_editable(False)
+
+ self._tagtable = gtk.TextTagTable()
+ hilite_tag = gtk.TextTag('search-hilite')
+ hilite_tag.props.background = '#FFFFB0'
+ self._tagtable.add(hilite_tag)
+ select_tag = gtk.TextTag('search-select')
+ select_tag.props.background = '#B0B0FF'
+ self._tagtable.add(select_tag)
+
+ scroll = gtk.ScrolledWindow()
+ scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scroll.add(self._textview)
+
+ scroll.set_size_request(gtk.gdk.screen_width()*70/100, 0)
+ self.pack_start(scroll, True, True, 0)
+
+ def _sort_logfile(self, treemodel, itera, iterb):
+ a = treemodel.get_value(itera, 0)
+ b = treemodel.get_value(iterb, 0)
+ if a == None or b == None:
+ return 0
+ a = a.lower()
+ b = b.lower()
+
+ # Filenames are parased as xxxx-YYY.log
+ # Sort first by xxxx, then numerically by YYY.
+ logre = re.compile(r'(.*)-(\d+)\.log', re.IGNORECASE)
+ ma = logre.match(a)
+ mb = logre.match(b)
+ if ma and mb:
+ if ma.group(1) > mb.group(1): return 1
+ if ma.group(1) < mb.group(1): return -1
+ if int(ma.group(2)) > int(mb.group(2)): return 1
+ if int(ma.group(2)) < int(mb.group(2)): return -1
+ return 0
+ else:
+ if a > b: return 1
+ if a < b: return -1
+ return 0
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 p in self.paths:
+ gnomevfs.monitor_add('file://' + p,
+ gnomevfs.MONITOR_DIRECTORY,
+ self._log_file_changed_cb)
- for f in self._extra_files:
+ 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)
+ dir, logfile = os.path.split(path)
if event == gnomevfs.MONITOR_EVENT_CHANGED:
- self._logs[filename].update()
+ if self.logs.has_key(logfile):
+ self.logs[logfile].update()
elif event == gnomevfs.MONITOR_EVENT_DELETED:
- self._delete_log_file_view(filename)
+ if self.logs.has_key(logfile):
+ self._remove_log_file(logfile)
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)
+ def _cursor_changed_cb(self, treeview):
+ treestore, iter = self._treeview.get_selection().get_selected()
+ self._show_log(treestore.get_value(iter, 0))
- for ext in self._extra_files:
- self._add_log_file(ext)
+ def _show_log(self, logfile):
+ if self.logs.has_key(logfile):
+ log = self.logs[logfile]
+ self._textview.set_buffer(log)
+ self._textview.scroll_to_mark(log.get_insert(), 0)
+ self.active_log = log
- return True
+ def _find_logs(self):
+ for path in self.paths:
+ try:
+ files = os.listdir(path)
+ except:
+ logging.debug(_("ERROR: Failed to look for files in '%(path)s'.") % {'path': path})
+ else:
+ for logfile in files:
+ self._add_log_file(os.path.join(path, logfile))
- def _delete_log_file_view(self, logkey):
- self._store_menu.remove(self._logs[logkey].iter)
- del self._logs[logkey]
+ for logfile in self.extra_files:
+ self._add_log_file(logfile)
- def _get_filename_from_path(self, path):
- return path.split('/')[-1]
+ self._treeview.expand_all()
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
+ logging.debug(_("ERROR: File '%(file)s' does not exist.") % {'file': path})
return False
if not os.access(path, os.R_OK):
- print "ERROR: I can't read '%s' file" % path
+ logging.debug(_("ERROR: Unable to read file '%(file)s'.") % {'file': path})
return False
- logfile = self._get_filename_from_path(path)
+ dir, logfile = os.path.split(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)
+ if not self.logs.has_key(logfile):
+ parent = self.extra_iter
+ if self.path_iter.has_key(dir):
+ parent = self.path_iter[dir]
+ iter = self._treemodel.append(parent, [logfile])
+
+ model = LogBuffer(self._tagtable, path, iter)
+ self.logs[logfile] = model
+
+ log = self.logs[logfile]
+ log.update()
+ written = log._written
+
+ if self.active_log == None:
+ self.active_log = log
+ self._show_log(logfile)
+ iter = self._treeview.get_model().convert_child_iter_to_iter(None, log.iter)
+ self._treeview.get_selection().select_iter(iter)
+
+ if written > 0 and self.active_log == log:
+ self._textview.scroll_to_mark(log.get_insert(), 0)
+
+ def _remove_log_file(self, logfile):
+ log = self.logs[logfile]
+ self._treemodel.remove(log.iter)
+ if self.active_log == log:
+ self.active_log = None
+ del self.logs[logfile]
+
+ def set_search_text(self, text):
+ self.search_text = text
- # 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)
+ buffer = self._textview.get_buffer()
- treeview.append_column(col_tv)
+ start, end = buffer.get_bounds()
+ buffer.remove_tag_by_name('search-hilite', start, end)
+ buffer.remove_tag_by_name('search-select', start, end)
- # 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)
+ iter = buffer.get_start_iter()
+ while True:
+ next = iter.forward_search(text, 0)
+ if next is None: break
+ start, end = next
+ buffer.apply_tag_by_name('search-hilite', start, end)
+ iter = end
+
+ if self.get_next_result('current'):
+ self.search_next('current')
+ elif self.get_next_result('backward'):
+ self.search_next('backward')
+
+ def get_next_result(self, dir):
+ buffer = self._textview.get_buffer()
+
+ if dir == 'forward':
+ iter = buffer.get_iter_at_mark(buffer.get_insert())
+ iter.forward_char()
+ else:
+ iter = buffer.get_iter_at_mark(buffer.get_insert())
- return iter
+ if dir == 'backward':
+ return iter.backward_search(self.search_text, 0)
+ else:
+ return iter.forward_search(self.search_text, 0)
+
+ def search_next(self, dir):
+ next = self.get_next_result(dir)
+ if next:
+ buffer = self._textview.get_buffer()
+
+ start, end = buffer.get_bounds()
+ buffer.remove_tag_by_name('search-select', start, end)
+
+ start, end = next
+ buffer.apply_tag_by_name('search-select', start, end)
+
+ buffer.place_cursor(start)
+
+ self._textview.scroll_to_iter(start, 0.1)
+ self._textview.scroll_to_iter(end, 0.1)
class LogBuffer(gtk.TextBuffer):
- def __init__(self, logfile, iter=None):
- gtk.TextBuffer.__init__(self)
+ def __init__(self, tagtable, logfile, iter):
+ gtk.TextBuffer.__init__(self, tagtable)
- self._logfile = logfile
+ self.logfile = logfile
self._pos = 0
self.iter = iter
self.update()
+ def append_formatted_text(self, text):
+ # Remove ANSI escape codes.
+ # todo- Handle a subset of them.
+ strip_ansi = re.compile(r'\033\[[\d;]*m')
+ text = strip_ansi.sub('', text)
+ self.insert(self.get_end_iter(), text.encode('utf8'))
+
def update(self):
try:
- f = open(self._logfile, 'r')
+ f = open(self.logfile, 'r')
init_pos = self._pos
-
+
f.seek(self._pos)
- self.insert(self.get_end_iter(), f.read())
+ self.append_formatted_text(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.insert(self.get_end_iter(), _("Error: Can't open file '%s'\n") % self.logfile)
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):
+class LogActivity(activity.Activity):
def __init__(self, handle):
activity.Activity.__init__(self, handle)
- logging.debug('Starting the Log Viewer activity')
- self.set_title(_('Log Viewer Activity'))
+ self.set_title(_('Log'))
- # Main path to watch: ~/.sugar/someuser/logs...
- main_path = os.path.join(env.get_profile_path(), 'logs')
+ # Paths to watch: ~/.sugar/someuser/logs, /var/log
+ paths = []
+ paths.append(env.get_profile_path('logs'))
+ paths.append('/var/log')
- # extra files to watch in logviewer
+ # Additional misc files.
ext_files = []
- ext_files.append("/var/log/Xorg.0.log")
- ext_files.append("/var/log/syslog")
- ext_files.append("/var/log/messages")
+ ext_files.append(os.path.expanduser('~/.bash_history'))
- self._viewer = MultiLogView(main_path, ext_files).hbox
+ self.viewer = MultiLogView(paths, ext_files)
+ self.set_canvas(self.viewer)
- self._box = gtk.HBox()
- self._box.pack_start(self._viewer)
- self._box.show()
-
- self.set_canvas(self._box)
+ self._build_toolbox()
+
+ self.show()
- # TOOLBAR
+ def _build_toolbox(self):
toolbox = activity.ActivityToolbox(self)
- toolbox.show()
-
- toolbar = LogToolbar(self)
- toolbox.add_toolbar(_('Tools'), toolbar)
- toolbar.show()
+ edit_toolbar = activity.EditToolbar()
+
+ edit_toolbar.paste.props.visible = False
+ edit_toolbar.undo.props.visible = False
+ edit_toolbar.redo.props.visible = False
+ edit_toolbar.separator.props.visible = False
+ edit_toolbar.copy.connect('clicked', self._copy_cb)
+
+ wrap_btn = ToggleToolButton('format-justify-left')
+ wrap_btn.set_tooltip(_('Word Wrap'))
+ wrap_btn.connect('clicked', self._wrap_cb)
+ wrap_btn.show()
+ edit_toolbar.insert(wrap_btn, -1)
+
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(False)
+ separator.set_expand(True)
+ edit_toolbar.insert(separator, -1)
+ separator.show()
+
+ search_item = gtk.ToolItem()
+
+ self._search_entry = iconentry.IconEntry()
+ self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
+ 'system-search')
+ self._search_entry.add_clear_button()
+ self._search_entry.connect('activate', self._search_entry_activate_cb)
+ self._search_entry.connect('changed', self._search_entry_changed_cb)
+
+ width = int(gtk.gdk.screen_width() / 3)
+ self._search_entry.set_size_request(width, -1)
+ self._search_entry.show()
+ search_item.add(self._search_entry)
+
+ search_item.show()
+ edit_toolbar.insert(search_item, -1)
+
+ self._search_prev = ToolButton('go-previous-paired')
+ self._search_prev.set_tooltip(_('Previous'))
+ #self._search_prev.props.sensitive = False
+ self._search_prev.connect('clicked', self._search_prev_cb)
+ self._search_prev.show()
+ edit_toolbar.insert(self._search_prev, -1)
+
+ self._search_next = ToolButton('go-next-paired')
+ self._search_next.set_tooltip(_('Next'))
+ #self._search_next.props.sensitive = False
+ self._search_next.connect('clicked', self._search_next_cb)
+ self._search_next.show()
+ edit_toolbar.insert(self._search_next, -1)
+
+ self._update_search_buttons()
+
+ edit_toolbar.show()
+ toolbox.add_toolbar(_('Edit'), edit_toolbar)
+
+ tools_toolbar = gtk.Toolbar()
+
+ delete_btn = ToolButton('list-remove')
+ delete_btn.set_tooltip(_('Delete Log File'))
+ delete_btn.connect('clicked', self._delete_log_cb)
+ delete_btn.show()
+ tools_toolbar.insert(delete_btn, -1)
+
+ separator = gtk.SeparatorToolItem()
+ separator.set_expand(True)
+ separator.set_draw(False)
+ separator.show()
+ tools_toolbar.insert(separator, -1)
+
+ self.collector_palette = CollectorPalette(self)
+ collector_btn = ToolButton('zoom-best-fit')
+ collector_btn.set_palette(self.collector_palette)
+ collector_btn.connect('clicked', self._logviewer_cb)
+ collector_btn.show()
+ tools_toolbar.insert(collector_btn, -1)
+
+ tools_toolbar.show()
+ toolbox.add_toolbar(_('Tools'), tools_toolbar)
+
+ toolbox.show()
self.set_toolbox(toolbox)
- self.show()
- # Dirty hide()
+ # Hide unsupported Activity tools.
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 _copy_cb(self, button):
+ if self.viewer.active_log:
+ self.viewer.active_log.copy_clipboard(gtk.clipboard_get())
+
+ def _wrap_cb(self, button):
+ if button.get_active():
+ self.viewer._textview.set_wrap_mode(gtk.WRAP_WORD_CHAR)
+ else:
+ self.viewer._textview.set_wrap_mode(gtk.WRAP_NONE)
- def _on_logviewer_clicked_cb(self, widget):
- self._handler.switch_to_logviewer()
+ def _search_entry_activate_cb(self, entry):
+ self.viewer.set_search_text(entry.props.text)
+ self._update_search_buttons()
-class CollectorMenu(Palette):
+ def _search_entry_changed_cb(self, entry):
+ self.viewer.set_search_text(entry.props.text)
+ self._update_search_buttons()
+
+ def _search_prev_cb(self, button):
+ self.viewer.search_next('backward')
+ self._update_search_buttons()
+
+ def _search_next_cb(self, button):
+ self.viewer.search_next('forward')
+ self._update_search_buttons()
+
+ def _update_search_buttons(self,):
+ if len(self.viewer.search_text) == 0:
+ self._search_prev.props.sensitive = False
+ self._search_next.props.sensitive = False
+ else:
+ prev = self.viewer.get_next_result('backward')
+ next = self.viewer.get_next_result('forward')
+ self._search_prev.props.sensitive = prev != None
+ self._search_next.props.sensitive = next != None
+
+ def _delete_log_cb(self, widget):
+ if self.viewer.active_log:
+ logfile = self.viewer.active_log.logfile
+ try:
+ os.remove(logfile)
+ except OSError, err:
+ notify = NotifyAlert()
+ notify.props.title = _('Error')
+ notify.props.msg = _('%(error)s when deleting %(file)s') % \
+ { 'error': err.strerror, 'file': logfile }
+ notify.connect('response', _notify_response_cb, self)
+ self.add_alert(notify)
+
+ def _logviewer_cb(self, widget):
+ self.collector_palette.popup(True)
+
+class CollectorPalette(Palette):
_DEFAULT_SERVER = 'http://olpc.scheffers.net/olpc/submit.tcl'
- def __init__(self):
- Palette.__init__(self, 'Log Collector: send XO information')
+ def __init__(self, handler):
+ Palette.__init__(self, _('Log Collector: Send XO information'))
+ self._handler = handler
+
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'))
+
+ label = gtk.Label(
+ _('Log collector sends information about the system\n'\
+ 'and running processes to a central server. Use\n'\
+ 'this option if you want to report a problem.'))
send_button = gtk.Button(_('Send information'))
send_button.connect('clicked', self._on_send_button_clicked_cb)
@@ -301,14 +509,30 @@ want to report\nsome detected problem'))
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"
+ success = True
+ try:
+ data = self._collector.write_logs()
+ sender = LogSend()
+ success = sender.http_post_logs(self._DEFAULT_SERVER, data)
+ except:
+ success = False
os.remove(data)
- self.popdown()
+ self.popdown(True)
+
+ title = ''
+ msg = ''
+ if success:
+ title = _('Logs sent')
+ msg = _('The logs were uploaded to the server.')
+ else:
+ title = _('Logs not sent')
+ msg = _('The logs could not be uploaded to the server. '\
+ 'Please check your network connection.')
+
+ notify = NotifyAlert()
+ notify.props.title = title
+ notify.props.msg = msg
+ notify.connect('response', _notify_response_cb, self._handler)
+ self._handler.add_alert(notify)
+