#!/usr/bin/env python # # Author: Sascha Silbe # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 # as published by the Free Software Foundation. # # 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 """Janitor. Activity to perform housekeeping tasks on the Sugar Journal. """ from gettext import gettext as _ import logging import os import gobject import gtk import pango from sugar.activity.widgets import ActivityToolbarButton from sugar.activity.widgets import StopButton from sugar.activity import activity from sugar.datastore import datastore import sugar.env from sugar.graphics.toolbutton import ToolButton from sugar.graphics.toolbarbox import ToolbarBox from sugar.graphics.toolbarbox import ToolbarButton import sugar.logger def _get_text_width(text): """Determine width (in pixels) of given text using default font. """ # XXX: is there really no easier way to achieve this? label = gtk.Label() font_layout = label.create_pango_layout(text) font_name = gtk.settings_get_default().props.gtk_font_name font_desc = pango.FontDescription(font_name) font_layout.set_font_description(font_desc) width, height = font_layout.get_pixel_size() return width def format_size(size): if not size: return _('Empty') elif size < 1024: return _('%4d B') % size elif size < 1024**2: return _('%4d KB') % (size / 1024) elif size < 1024**3: return _('%4d MB') % (size / 1024**2) else: return _('%4d GB') % (size / 1024**3) class JanitorActivity(activity.Activity): _store_columns = [ gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_UINT64, gobject.TYPE_FLOAT, gobject.TYPE_STRING, ] NUM_COLUMNS = len(_store_columns) COLUMN_SELECT, COLUMN_TITLE, COLUMN_SIZE_INT, COLUMN_SIZE_PERCENT, \ COLUMN_SIZE_TEXT = range(NUM_COLUMNS) _view_columns = [ {'name': 'Select', 'renderer': gtk.CellRendererToggle, 'view_args': {'active': COLUMN_SELECT}, 'callback': lambda cell, path, self: self._select_toggled(cell, path), }, {'name': 'Title', 'renderer': gtk.CellRendererText, 'ellipsize': True, 'view_args': {'text': COLUMN_TITLE}, 'sort_column': COLUMN_TITLE, }, {'name': 'Size', 'size': max(_get_text_width('XX 9999.9 GB XX'), _get_text_width(format_size(0)),), 'renderer': gtk.CellRendererProgress, 'view_args': {'value': COLUMN_SIZE_PERCENT, 'text': COLUMN_SIZE_TEXT}, 'sort_column': COLUMN_SIZE_INT, }, ] _properties = [ 'uid', 'tree_id', 'version_id', 'title', 'timestamp', ] def __init__(self, handle): activity.Activity.__init__(self, handle) self.max_participants = 1 self._largest_size = 0 self._setup_widgets() def _setup_widgets(self): self._setup_toolbar() self._setup_canvas() def _setup_canvas(self): vbox = gtk.VBox() scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) self._create_list_model() self._list_view = gtk.TreeView(self._list_model) self._list_view.set_rules_hint(True) scrolled_window.add(self._list_view) self._setup_list_view_columns() sort_column_idx = [idx for idx, column in enumerate(self._view_columns) if column['name'] == 'Size'][0] self._list_model.set_sort_column_id(sort_column_idx, gtk.SORT_DESCENDING) self._list_view.show() scrolled_window.show() vbox.add(scrolled_window) self.set_canvas(vbox) vbox.show() def _setup_toolbar(self): toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() separator = gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) separator.show() stop_button = StopButton(self) toolbar_box.toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbar_box) toolbar_box.show() def _setup_list_view_columns(self): for idx, column in enumerate(self._view_columns): renderer = column['renderer']() if column.get('ellipsize'): renderer.props.ellipsize = pango.ELLIPSIZE_MIDDLE renderer.props.ellipsize_set = True if 'callback' in column: if isinstance(renderer, gtk.CellRendererToggle): signal_name = 'toggled' else: raise ValueError('Unknown renderer') renderer.connect(signal_name, column['callback'], self) view_column = gtk.TreeViewColumn(_(column['name']), renderer, **column['view_args']) if isinstance(renderer, gtk.CellRendererText): view_column.props.expand = True if 'sort_column' in column: view_column.set_sort_column_id(column['sort_column']) if 'size' in column: view_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED view_column.props.fixed_width = column['size'] self._list_view.append_column(view_column) def _create_list_model(self): self._list_model = gtk.ListStore(*[ column_type for column_type in self._store_columns]) self._refresh_list() def _refresh_list(self): self._list_model.clear() # FIXME: async # FIXME: listen for DBus signals jobjects = datastore.find({}, properties=self._properties)[0] entries = [] for jobject in jobjects: metadata = jobject.metadata size = self._get_size(jobject) # TODO: handle broken (non-UTF8) metadata entries.append({ 'size': size, 'title': metadata.get('title', ''), }) jobject.destroy() del jobjects largest_size = reduce(max, [entry['size'] for entry in entries]) for entry in entries: giter = self._list_model.append() percent = float(entry['size']) / largest_size * 100 self._list_model.set(giter, self.COLUMN_TITLE, entry['title'], self.COLUMN_SIZE_INT, entry['size'], self.COLUMN_SIZE_PERCENT, percent, self.COLUMN_SIZE_TEXT, format_size(entry['size'])) def _get_size(self, jobject): """Return the file size for a Journal object.""" logging.debug('get_file_size %r', jobject.object_id) path = jobject.get_file_path() if not path: return 0 return os.stat(path).st_size def _select_toggled(self, cell, path): """Called when a 'Select' cell is toggled.""" giter = self._list_model.get_iter((int(path), )) selected = self._list_model.get_value(giter, self.COLUMN_SELECT) self._list_model.set(giter, self.COLUMN_SELECT, not selected) sugar.logger.start()