diff options
author | Sascha Silbe <sascha-pgp@silbe.org> | 2010-08-29 12:07:50 (GMT) |
---|---|---|
committer | Sascha Silbe <sascha-pgp@silbe.org> | 2010-08-29 12:07:50 (GMT) |
commit | d135159e5eb43ad6b4e456eb4fee31579d8806c9 (patch) | |
tree | d769f115f386f87ed9215ba12eb873bca58b9b40 | |
parent | 779341f684e0996bbf1bde1e4b2df868545bcf80 (diff) |
add UI for choosing the target path, use unicode
-rw-r--r-- | backup.py | 142 |
1 files changed, 125 insertions, 17 deletions
@@ -13,13 +13,14 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """Backup. Activity to back up the Sugar Journal to external media. """ -from gettext import gettext as _ + +import gettext import logging import os import select +import statvfs import sys import tempfile import time @@ -27,6 +28,7 @@ import traceback import zipfile import dbus +import gconf import gobject import gtk @@ -34,8 +36,11 @@ import gtk from sugar.activity.widgets import StopButton from sugar.activity import activity import sugar.env +from sugar.graphics.icon import CellRendererIcon +from sugar.graphics import style from sugar.graphics.toolbutton import ToolButton from sugar.graphics.toolbarbox import ToolbarBox +from sugar.graphics.xocolor import XoColor import sugar.logger from sugar import profile @@ -69,7 +74,7 @@ class BackupButton(ToolButton): def __init__(self, **kwargs): ToolButton.__init__(self, 'journal-export', **kwargs) - self.props.tooltip = _('Backup Journal') + self.props.tooltip = _('Backup Journal').encode('utf-8') self.props.accelerator = '<Alt>b' @@ -138,7 +143,8 @@ class AsyncBackup(gobject.GObject): """Receive and handle message from child.""" if condition in [gobject.IO_ERR, gobject.IO_HUP]: logging.debug('error condition: %r', condition) - self.emit('error', _('Lost connection to child process')) + self.emit('error', + _('Lost connection to child process').encode('utf-8')) self._parent_close() return False @@ -158,7 +164,7 @@ class AsyncBackup(gobject.GObject): message = unicode(self._read_line_from_child(), 'utf-8') trace = unicode(self._pipe_from_child.read().strip(), 'utf-8') logging.error('Child reported error: %s\n%s', message, trace) - self.emit('error', message) + self.emit('error', message.encode('utf-8')) self._parent_close() return False @@ -181,11 +187,11 @@ class AsyncBackup(gobject.GObject): """ line = [] while True: - c = os.read(self._pipe_from_child.fileno(), 1) - if c == '\n': + character = os.read(self._pipe_from_child.fileno(), 1) + if character == '\n': return ''.join(line) - line.append(c) + line.append(character) def _parent_close(self): """Close connections to child and wait for it.""" @@ -367,14 +373,24 @@ class AsyncBackup(gobject.GObject): class BackupActivity(activity.Activity): _METADATA_JSON_NAME = '_metadata.json' + _MEDIA_COMBO_ICON_COLUMN = 0 + _MEDIA_COMBO_NAME_COLUMN = 1 + _MEDIA_COMBO_PATH_COLUMN = 2 + _MEDIA_COMBO_FREE_COLUMN = 3 def __init__(self, handle): activity.Activity.__init__(self, handle, create_jobject=False) self.max_participants = 1 self._progress_bar = None self._message_box = None + self._media_combo_model = None + self._media_combo = None self._backup = None + client = gconf.client_get_default() + self._color = XoColor(client.get_string('/desktop/sugar/user/color')) self._setup_widgets() + self._find_media() + self._media_combo.set_active(0) def read_file(self, file_path): """We don't have any state to save in the Journal.""" @@ -400,28 +416,54 @@ class BackupActivity(activity.Activity): def _setup_toolbar(self): toolbar_box = ToolbarBox() + self._media_combo_model = gtk.ListStore(str, str, str, str) + self._media_combo = gtk.ComboBox(self._media_combo_model) + icon_renderer = CellRendererIcon(self._media_combo) + icon_renderer.props.xo_color = self._color + icon_renderer.props.width = style.STANDARD_ICON_SIZE + \ + style.DEFAULT_SPACING + icon_renderer.props.height = style.STANDARD_ICON_SIZE + icon_renderer.props.size = style.STANDARD_ICON_SIZE + icon_renderer.props.xpad = style.DEFAULT_PADDING + icon_renderer.props.ypad = style.DEFAULT_PADDING + self._media_combo.pack_start(icon_renderer, False) + self._media_combo.add_attribute(icon_renderer, 'icon_name', + self._MEDIA_COMBO_ICON_COLUMN) + name_renderer = gtk.CellRendererText() + self._media_combo.pack_start(name_renderer, False) + self._media_combo.add_attribute(name_renderer, 'text', + self._MEDIA_COMBO_NAME_COLUMN) + free_renderer = gtk.CellRendererText() + self._media_combo.pack_start(free_renderer, False) + self._media_combo.add_attribute(free_renderer, 'text', + self._MEDIA_COMBO_FREE_COLUMN) + + tool_item = gtk.ToolItem() + tool_item.add(self._media_combo) + # FIXME: looks like plain GTK, not like Sugar + tooltip_text = _('Storage medium to store the backup on') + tool_item.set_tooltip_text(tooltip_text.encode('utf-8')) + toolbar_box.toolbar.insert(tool_item, -1) + backup_button = BackupButton() backup_button.connect('clicked', self.__backup_cb) toolbar_box.toolbar.insert(backup_button, -1) - backup_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() + toolbar_box.show_all() def __backup_cb(self, button): """Callback for Backup button.""" - # FIXME - mount_point = '/media/tmp' + row = self._media_combo_model[self._media_combo.get_active()] + mount_point = row[self._MEDIA_COMBO_PATH_COLUMN] self._setup_backup_view(mount_point) self._start_backup(mount_point) @@ -437,7 +479,8 @@ class BackupActivity(activity.Activity): """Set up UI for showing feedback from worker process.""" vbox = gtk.VBox(False) - label = gtk.Label(_('Backing up Journal to %s') % (mount_point, )) + label_text = _('Backing up Journal to %s') % (mount_point, ) + label = gtk.Label(label_text.encode('utf-8')) label.show() vbox.pack_start(label) @@ -445,6 +488,7 @@ class BackupActivity(activity.Activity): alignment.show() self._progress_bar = gtk.ProgressBar() + self._progress_bar.props.text = _('Scanning Journal').encode('utf-8') self._progress_bar.show() alignment.add(self._progress_bar) vbox.add(alignment) @@ -477,11 +521,11 @@ class BackupActivity(activity.Activity): def _error_cb(self, backup, message): """Receive error message from child process.""" - self._show_error(message) + self._show_error(unicode(message, 'utf-8')) def _show_error(self, message): """Present error message to user.""" - self._message_box.props.label = message + self._message_box.props.label = unicode(message).encode('utf-8') self._message_box.show() # def _close_cb(self, button): @@ -490,5 +534,69 @@ class BackupActivity(activity.Activity): # self.emit('close') + def _find_media(self): + """Fill the combo box with available storage media. + Also sets up a callback to keep the list current. + """ + try: + import gio + except ImportError: + return self._find_media_hal() + + volume_monitor = gio.volume_monitor_get() + volume_monitor.connect('mount-added', self._gio_mount_added_cb) + volume_monitor.connect('mount-removed', self._gio_mount_removed_cb) + for mount in volume_monitor.get_mounts(): + self._gio_mount_added_cb(mount) + + def _find_media_hal(self): + # FIXME: implement HAL support + pass + + def _gio_mount_added_cb(self, mount): + """Handle notification from GIO that a medium was mounted.""" + icon_name = self._choose_icon_name(mount.get_icon().props.names) + path = mount.get_root().get_path() + name = mount.get_name() + self._add_medium(path, name, icon_name) + + def _gio_mount_removed_cb(self, mount): + """Handle notification from GIO that a medium was unmounted.""" + path = mount.get_root().get_path() + self._remove_medium(path) + + def _add_medium(self, path, medium_name, icon_name): + """Make storage medium selectable in the UI.""" + # FIXME: update space information periodically or at least after + # backup run + stat = os.statvfs(path) + free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL] +# total_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BLOCKS] + self._media_combo_model.append([icon_name, medium_name, path, + _('%s Free') % (format_size(free_space), )]) + + def _remove_medium(self, path): + """Remove storage medium from UI.""" + for position, row in enumerate(self._media_combo_model): + if path == row[self._MEDIA_COMBO_PATH_COLUMN]: + del self._media_combo_model[position] + return + + logging.warning("Asked to remove %s from UI, but didn't find it!", + path) + + def _choose_icon_name(self, names): + """Choose the first valid icon name or fall back to a default name.""" + theme = gtk.icon_theme_get_default() + for name in names: + if theme.lookup_icon(name, gtk.ICON_SIZE_LARGE_TOOLBAR, 0): + return name + + return 'drive' + + +# pylint isn't smart enough for the gettext.install() magic +_ = lambda msg: msg +gettext.install('backup', 'po', unicode=True) sugar.logger.start() |