Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSascha 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)
commitd135159e5eb43ad6b4e456eb4fee31579d8806c9 (patch)
treed769f115f386f87ed9215ba12eb873bca58b9b40
parent779341f684e0996bbf1bde1e4b2df868545bcf80 (diff)
add UI for choosing the target path, use unicode
-rw-r--r--backup.py142
1 files changed, 125 insertions, 17 deletions
diff --git a/backup.py b/backup.py
index 736ba25..2fcc402 100644
--- a/backup.py
+++ b/backup.py
@@ -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()