diff options
Diffstat (limited to 'rpms/sugar/0019-Journal-Volumes-Backup-and-Restore.patch')
-rw-r--r-- | rpms/sugar/0019-Journal-Volumes-Backup-and-Restore.patch | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/rpms/sugar/0019-Journal-Volumes-Backup-and-Restore.patch b/rpms/sugar/0019-Journal-Volumes-Backup-and-Restore.patch new file mode 100644 index 0000000..4b1594a --- /dev/null +++ b/rpms/sugar/0019-Journal-Volumes-Backup-and-Restore.patch @@ -0,0 +1,700 @@ +From a0a18343e1823367813de3ec3cea579202ae5ea4 Mon Sep 17 00:00:00 2001 +From: Martin Abente <mabente@paraguayeduca.org> +Date: Tue, 22 Jun 2010 15:59:13 -0400 +Subject: [PATCH sugar 19/74] Journal Volumes Backup and Restore + +Add a basic backup and restore feature for the Sugar Journal. +It provides: + +- Generic Backup and Restore dialog GUI. +- Process manager class as an abstraction layer between the dialog and + backup/restore scripts. (Allowing to work with many backup and restore + technologies, using the same GUI, with no need for script rewrite). +- Basic file system Volume Restore and Backup scripts implemented in Python. +- New backup and restore options for journal volumes palettes. + +This patch is based on Esteban Arias (Plan Ceibal) Volume Backup and Restore +patch, with a few changes: + +- Refactor original Backup dialog class into a generic dialog class. +- Create specialized VolumeBackupDialog and VolumeRestoreDialog subclasses. +- Rewrite backup and restore scripts in python for an easier sugar interaction. +- Add backup identification helpers to jarabe.journal.misc. +--- + bin/Makefile.am | 4 +- + bin/journal-backup-volume | 57 ++++++++ + bin/journal-restore-volume | 67 +++++++++ + src/jarabe/journal/Makefile.am | 3 +- + src/jarabe/journal/misc.py | 27 ++++ + src/jarabe/journal/processdialog.py | 248 +++++++++++++++++++++++++++++++++ + src/jarabe/journal/volumestoolbar.py | 4 +- + src/jarabe/model/Makefile.am | 1 + + src/jarabe/model/processmanagement.py | 98 +++++++++++++ + src/jarabe/view/palettes.py | 44 ++++++ + 10 files changed, 549 insertions(+), 4 deletions(-) + create mode 100644 bin/journal-backup-volume + create mode 100644 bin/journal-restore-volume + create mode 100644 src/jarabe/journal/processdialog.py + create mode 100644 src/jarabe/model/processmanagement.py + +diff --git a/bin/Makefile.am b/bin/Makefile.am +index 05a9215..65aab9b 100644 +--- a/bin/Makefile.am ++++ b/bin/Makefile.am +@@ -5,7 +5,9 @@ python_scripts = \ + sugar-install-bundle \ + sugar-launch \ + sugar-session \ +- sugar-ui-check ++ sugar-ui-check \ ++ journal-backup-volume \ ++ journal-restore-volume + + bin_SCRIPTS = \ + sugar \ +diff --git a/bin/journal-backup-volume b/bin/journal-backup-volume +new file mode 100644 +index 0000000..9246760 +--- /dev/null ++++ b/bin/journal-backup-volume +@@ -0,0 +1,57 @@ ++#!/usr/bin/env python ++# Copyright (C) 2010, Paraguay Educa <tecnologia@paraguayeduca.org> ++# ++# 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 3 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, see <http://www.gnu.org/licenses/>. ++# ++ ++import os ++import sys ++import subprocess ++import logging ++ ++from sugar import env ++#from sugar.datastore import datastore ++ ++backup_identifier = sys.argv[2] ++volume_path = sys.argv[1] ++ ++if len(sys.argv) != 3: ++ print 'Usage: %s <volume_path> <backup_identifier>' % sys.argv[0] ++ exit(1) ++ ++logging.debug('Backup started') ++ ++backup_path = os.path.join(volume_path, 'backup', backup_identifier) ++ ++if not os.path.exists(backup_path): ++ os.makedirs(backup_path) ++ ++#datastore.freeze() ++#subprocess.call(['pkill', '-9', '-f', 'python.*datastore-service']) ++ ++result = 0 ++try: ++ cmd = ['tar', '-C', env.get_profile_path(), '-czf', \ ++ os.path.join(backup_path, 'datastore.tar.gz'), 'datastore'] ++ ++ subprocess.check_call(cmd) ++ ++except Exception, e: ++ logging.error('Backup failed: %s', str(e)) ++ result = 1 ++ ++#datastore.thaw() ++ ++logging.debug('Backup finished') ++exit(result) +diff --git a/bin/journal-restore-volume b/bin/journal-restore-volume +new file mode 100644 +index 0000000..aa14ad0 +--- /dev/null ++++ b/bin/journal-restore-volume +@@ -0,0 +1,67 @@ ++#!/usr/bin/env python ++# Copyright (C) 2010, Paraguay Educa <tecnologia@paraguayeduca.org> ++# ++# 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 3 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, see <http://www.gnu.org/licenses/>. ++# ++ ++import os ++import sys ++import shutil ++import logging ++import subprocess ++ ++from sugar import env ++#from sugar.datastore import datastore ++ ++backup_identifier = sys.argv[2] ++volume_path = sys.argv[1] ++ ++if len(sys.argv) != 3: ++ print 'Usage: %s <volume_path> <backup_identifier>' % sys.argv[0] ++ exit(1) ++ ++logging.debug('Restore started') ++ ++journal_path = os.path.join(env.get_profile_path(), 'datastore') ++backup_path = os.path.join(volume_path, 'backup', backup_identifier, 'datastore.tar.gz') ++ ++if not os.path.exists(backup_path): ++ logging.error('Could not find backup file %s', backup_path) ++ exit(1) ++ ++#datastore.freeze() ++subprocess.call(['pkill', '-9', '-f', 'python.*datastore-service']) ++ ++result = 0 ++try: ++ if os.path.exists(journal_path): ++ shutil.rmtree(journal_path) ++ ++ subprocess.check_call(['tar', '-C', env.get_profile_path(), '-xzf', backup_path]) ++ ++except Exception, e: ++ logging.error('Restore failed: %s', str(e)) ++ result = 1 ++ ++try: ++ shutil.rmtree(os.path.join(journal_path, 'index')) ++ os.remove(os.path.join(journal_path, 'index_updated')) ++ os.remove(os.path.join(journal_path, 'version')) ++except: ++ logging.debug('Restore has no index files') ++ ++#datastore.thaw() ++ ++logging.debug('Restore finished') ++exit(result) +diff --git a/src/jarabe/journal/Makefile.am b/src/jarabe/journal/Makefile.am +index ba29062..f24dcfe 100644 +--- a/src/jarabe/journal/Makefile.am ++++ b/src/jarabe/journal/Makefile.am +@@ -15,4 +15,5 @@ sugar_PYTHON = \ + model.py \ + objectchooser.py \ + palettes.py \ +- volumestoolbar.py ++ volumestoolbar.py \ ++ processdialog.py +diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py +index 1431d5f..fac56ad 100644 +--- a/src/jarabe/journal/misc.py ++++ b/src/jarabe/journal/misc.py +@@ -1,4 +1,5 @@ + # Copyright (C) 2007, One Laptop Per Child ++# Copyright (C) 2010, Paraguay Educa <tecnologia@paraguayeduca.org> + # + # 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 +@@ -313,3 +314,29 @@ def get_icon_color(metadata): + return XoColor(client.get_string('/desktop/sugar/user/color')) + else: + return XoColor(metadata['icon-color']) ++ ++def get_backup_identifier(): ++ serial_number = get_xo_serial() ++ if serial_number is None: ++ serial_number = get_nick() ++ return serial_number ++ ++def get_xo_serial(): ++ path = '/ofw/serial-number' ++ ++ if os.access(path, os.R_OK) == 0: ++ return None ++ ++ file_descriptor = open(path, 'r') ++ content = file_descriptor.read() ++ file_descriptor.close() ++ ++ if content: ++ return content.strip() ++ else: ++ logging.error('No serial number at %s', path) ++ return None ++ ++def get_nick(): ++ client = gconf.client_get_default() ++ return client.get_string("/desktop/sugar/user/nick") +diff --git a/src/jarabe/journal/processdialog.py b/src/jarabe/journal/processdialog.py +new file mode 100644 +index 0000000..b96abd9 +--- /dev/null ++++ b/src/jarabe/journal/processdialog.py +@@ -0,0 +1,248 @@ ++#!/usr/bin/env python ++# Copyright (C) 2010, Plan Ceibal <comunidad@plan.ceibal.edu.uy> ++# Copyright (C) 2010, Paraguay Educa <tecnologia@paraguayeduca.org> ++# ++# 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 3 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, see <http://www.gnu.org/licenses/>. ++ ++import gtk ++import gobject ++import gconf ++import logging ++ ++from gettext import gettext as _ ++from sugar.graphics import style ++from sugar.graphics.icon import Icon ++from sugar.graphics.xocolor import XoColor ++ ++from jarabe.journal import misc ++from jarabe.model import shell ++from jarabe.model import processmanagement ++from jarabe.model.session import get_session_manager ++ ++class ProcessDialog(gtk.Window): ++ ++ __gtype_name__ = 'SugarProcessDialog' ++ ++ def __init__(self, process_script='', process_params=[], restart_after=True): ++ ++ #FIXME: Workaround limitations of Sugar core modal handling ++ shell_model = shell.get_model() ++ shell_model.set_zoom_level(shell_model.ZOOM_HOME) ++ ++ gtk.Window.__init__(self) ++ ++ self._process_script = processmanagement.find_and_absolutize(process_script) ++ self._process_params = process_params ++ self._restart_after = restart_after ++ self._start_message = _('Running') ++ self._failed_message = _('Failed') ++ self._finished_message = _('Finished') ++ ++ self.set_border_width(style.LINE_WIDTH) ++ width = gtk.gdk.screen_width() ++ height = gtk.gdk.screen_height() ++ self.set_size_request(width, height) ++ self.set_position(gtk.WIN_POS_CENTER_ALWAYS) ++ self.set_decorated(False) ++ self.set_resizable(False) ++ self.set_modal(True) ++ ++ self._colored_box = gtk.EventBox() ++ self._colored_box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white")) ++ self._colored_box.show() ++ ++ self._vbox = gtk.VBox() ++ self._vbox.set_spacing(style.DEFAULT_SPACING) ++ self._vbox.set_border_width(style.GRID_CELL_SIZE) ++ ++ self._colored_box.add(self._vbox) ++ self.add(self._colored_box) ++ ++ self._setup_information() ++ self._setup_progress_bar() ++ self._setup_options() ++ ++ self._vbox.show() ++ ++ self.connect("realize", self.__realize_cb) ++ ++ self._process_management = processmanagement.ProcessManagement() ++ self._process_management.connect('process-management-running', self._set_status_updated) ++ self._process_management.connect('process-management-started', self._set_status_started) ++ self._process_management.connect('process-management-finished', self._set_status_finished) ++ self._process_management.connect('process-management-failed', self._set_status_failed) ++ ++ def _setup_information(self): ++ client = gconf.client_get_default() ++ color = XoColor(client.get_string('/desktop/sugar/user/color')) ++ ++ self._icon = Icon(icon_name='activity-journal', pixel_size=style.XLARGE_ICON_SIZE, xo_color=color) ++ self._icon.show() ++ ++ self._vbox.pack_start(self._icon, False) ++ ++ self._title = gtk.Label() ++ self._title.modify_fg(gtk.STATE_NORMAL, style.COLOR_BLACK.get_gdk_color()) ++ self._title.set_use_markup(True) ++ self._title.set_justify(gtk.JUSTIFY_CENTER) ++ self._title.show() ++ ++ self._vbox.pack_start(self._title, False) ++ ++ self._message = gtk.Label() ++ self._message.modify_fg(gtk.STATE_NORMAL, style.COLOR_BLACK.get_gdk_color()) ++ self._message.set_use_markup(True) ++ self._message.set_line_wrap(True) ++ self._message.set_justify(gtk.JUSTIFY_CENTER) ++ self._message.show() ++ ++ self._vbox.pack_start(self._message, True) ++ ++ def _setup_options(self): ++ hbox = gtk.HBox(True, 3) ++ hbox.show() ++ ++ icon = Icon(icon_name='dialog-ok') ++ ++ self._start_button = gtk.Button() ++ self._start_button.set_image(icon) ++ self._start_button.set_label(_('Start')) ++ self._start_button.connect('clicked', self.__start_cb) ++ self._start_button.show() ++ ++ icon = Icon(icon_name='dialog-cancel') ++ ++ self._close_button = gtk.Button() ++ self._close_button.set_image(icon) ++ self._close_button.set_label(_('Cancel')) ++ self._close_button.connect('clicked', self.__close_cb) ++ self._close_button.show() ++ ++ icon = Icon(icon_name='system-restart') ++ ++ self._restart_button = gtk.Button() ++ self._restart_button.set_image(icon) ++ self._restart_button.set_label(_('Restart')) ++ self._restart_button.connect('clicked', self.__restart_cb) ++ self._restart_button.hide() ++ ++ hbox.add(self._start_button) ++ hbox.add(self._close_button) ++ hbox.add(self._restart_button) ++ ++ halign = gtk.Alignment(1, 0, 0, 0) ++ halign.show() ++ halign.add(hbox) ++ ++ self._vbox.pack_start(halign, False, False, 3) ++ ++ def _setup_progress_bar(self): ++ alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.5) ++ alignment.show() ++ ++ self._progress_bar = gtk.ProgressBar(adjustment=None) ++ self._progress_bar.hide() ++ ++ alignment.add(self._progress_bar) ++ self._vbox.pack_start(alignment) ++ ++ def __realize_cb(self, widget): ++ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) ++ self.window.set_accept_focus(True) ++ ++ def __close_cb(self, button): ++ self.destroy() ++ ++ def __start_cb(self, button): ++ self._process_management.do_process([self._process_script] + self._process_params) ++ ++ def __restart_cb(self, button): ++ session_manager = get_session_manager() ++ session_manager.logout() ++ ++ def _set_status_started(self, model, data=None): ++ self._message.set_markup(self._start_message) ++ ++ self._start_button.hide() ++ self._close_button.hide() ++ ++ self._progress_bar.set_fraction(0.05) ++ self._progress_bar_handler = gobject.timeout_add(1000, self.__progress_bar_handler_cb) ++ self._progress_bar.show() ++ ++ def __progress_bar_handler_cb(self): ++ self._progress_bar.pulse() ++ return True ++ ++ def _set_status_updated(self, model, data): ++ pass ++ ++ def _set_status_finished(self, model, data=None): ++ self._message.set_markup(self._finished_message) ++ ++ self._progress_bar.hide() ++ self._start_button.hide() ++ ++ if self._restart_after: ++ self._restart_button.show() ++ else: ++ self._close_button.show() ++ ++ def _set_status_failed(self, model, error_message=''): ++ self._message.set_markup('%s %s' % (self._failed_message, error_message)) ++ ++ self._progress_bar.hide() ++ self._start_button.show() ++ self._close_button.show() ++ ++ logging.error(error_message) ++ ++ ++class VolumeBackupDialog(ProcessDialog): ++ ++ def __init__(self, volume_path): ++ ProcessDialog.__init__(self, 'journal-backup-volume', \ ++ [volume_path, misc.get_backup_identifier()], restart_after=False) ++ ++ self._resetup_information(volume_path) ++ ++ def _resetup_information(self, volume_path): ++ self._start_message = '%s %s. \n\n' % (_('Please wait, saving Journal content to'), volume_path) + \ ++ '<big><b>%s</b></big>' % _('Do not remove the storage device!') ++ ++ self._finished_message = _('The Journal content has been saved.') ++ ++ self._title.set_markup('<big><b>%s</b></big>' % _('Backup')) ++ ++ self._message.set_markup('%s %s' % (_('Journal content will be saved to'), volume_path)) ++ ++class VolumeRestoreDialog(ProcessDialog): ++ ++ def __init__(self, volume_path): ++ ProcessDialog.__init__(self, 'journal-restore-volume', \ ++ [volume_path, misc.get_backup_identifier()]) ++ ++ self._resetup_information(volume_path) ++ ++ def _resetup_information(self, volume_path): ++ self._start_message = '%s %s. \n\n' % (_('Please wait, restoring Journal content from'), volume_path) + \ ++ '<big><b>%s</b></big>' % _('Do not remove the storage device!') ++ ++ self._finished_message = _('The Journal content has been restored.') ++ ++ self._title.set_markup('<big><b>%s</b></big>' % _('Restore')) ++ ++ self._message.set_markup('%s %s.\n\n' % (_('Journal content will be restored from'), volume_path) + \ ++ '<big><b>%s</b> %s</big>' % (_('Warning:'), _('Current Journal content will be deleted!'))) ++ +diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py +index 71b6ea8..48e25ec 100644 +--- a/src/jarabe/journal/volumestoolbar.py ++++ b/src/jarabe/journal/volumestoolbar.py +@@ -36,7 +36,7 @@ + from sugar import env + + from jarabe.journal import model +-from jarabe.view.palettes import VolumePalette ++from jarabe.view.palettes import JournalVolumePalette + + + _JOURNAL_0_METADATA_DIR = '.olpc.store' +@@ -341,7 +341,7 @@ def __init__(self, mount): + self.props.xo_color = color + + def create_palette(self): +- palette = VolumePalette(self._mount) ++ palette = JournalVolumePalette(self._mount) + #palette.props.invoker = FrameWidgetInvoker(self) + #palette.set_group_id('frame') + return palette +diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am +index 92e8712..bd9bef2 100644 +--- a/src/jarabe/model/Makefile.am ++++ b/src/jarabe/model/Makefile.am +@@ -12,6 +12,7 @@ sugar_PYTHON = \ + neighborhood.py \ + network.py \ + notifications.py \ ++ processmanagement.py \ + shell.py \ + screen.py \ + session.py \ +diff --git a/src/jarabe/model/processmanagement.py b/src/jarabe/model/processmanagement.py +new file mode 100644 +index 0000000..466e1f6 +--- /dev/null ++++ b/src/jarabe/model/processmanagement.py +@@ -0,0 +1,98 @@ ++# Copyright (C) 2010, Paraguay Educa <tecnologia@paraguayeduca.org> ++# Copyright (C) 2010, Plan Ceibal <comunidad@plan.ceibal.edu.uy> ++# ++# 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 gobject ++import glib ++import gio ++ ++from sugar import env ++from gettext import gettext as _ ++ ++BYTES_TO_READ = 100 ++ ++class ProcessManagement(gobject.GObject): ++ ++ __gtype_name__ = 'ProcessManagement' ++ ++ __gsignals__ = { ++ 'process-management-running' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), ++ 'process-management-started' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), ++ 'process-management-finished' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), ++ 'process-management-failed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])) ++ } ++ ++ def __init__(self): ++ gobject.GObject.__init__(self) ++ self._running = False ++ ++ def do_process(self, cmd): ++ self._run_cmd_async(cmd) ++ ++ def _report_process_status(self, stream, result): ++ data = stream.read_finish(result) ++ ++ if len(data): ++ self.emit('process-management-running', data) ++ stream.read_async(BYTES_TO_READ, self._report_process_status) ++ ++ def _report_process_error(self, stream, result, concat_err=''): ++ data = stream.read_finish(result) ++ concat_err = concat_err + data ++ ++ if len(data) == 0: ++ self.emit('process-management-failed', concat_err) ++ else: ++ stream.read_async(BYTES_TO_READ, self._report_process_error, user_data=concat_err) ++ ++ def _notify_error(self, stderr): ++ stdin_stream = gio.unix.InputStream(stderr, True) ++ stdin_stream.read_async(BYTES_TO_READ, self._report_process_error) ++ ++ def _notify_process_status(self, stdout): ++ stdin_stream = gio.unix.InputStream(stdout, True) ++ stdin_stream.read_async(BYTES_TO_READ, self._report_process_status) ++ ++ def _run_cmd_async(self, cmd): ++ if self._running == False: ++ try: ++ pid, stdin, stdout, stderr = glib.spawn_async(cmd, flags=glib.SPAWN_DO_NOT_REAP_CHILD, standard_output=True, standard_error=True) ++ gobject.child_watch_add(pid, _handle_process_end, (self, stderr)) ++ except Exception: ++ self.emit('process-management-failed', _("Error - Call process: ") + str(cmd)) ++ else: ++ self._notify_process_status(stdout) ++ self._running = True ++ self.emit('process-management-started') ++ ++def _handle_process_end(pid, condition, (myself, stderr)): ++ myself._running = False ++ ++ if os.WIFEXITED(condition) and\ ++ os.WEXITSTATUS(condition) == 0: ++ myself.emit('process-management-finished') ++ else: ++ myself._notify_error(stderr) ++ ++def find_and_absolutize(script_name): ++ paths = env.os.environ['PATH'].split(':') ++ for path in paths: ++ looking_path = path + '/' + script_name ++ if env.os.path.isfile(looking_path): ++ return looking_path ++ ++ return None +diff --git a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py +index 3195c0c..a9e3629 100644 +--- a/src/jarabe/view/palettes.py ++++ b/src/jarabe/view/palettes.py +@@ -1,4 +1,6 @@ + # Copyright (C) 2008 One Laptop Per Child ++# Copyright (C) 2010, Plan Ceibal <comunidad@plan.ceibal.edu.uy> ++# Copyright (C) 2010, Paraguay Educa <tecnologia@paraguayeduca.org> + # + # 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 +@@ -31,6 +33,7 @@ + from sugar.graphics.xocolor import XoColor + from sugar.activity.i18n import pgettext + ++from jarabe.journal.processdialog import VolumeBackupDialog, VolumeRestoreDialog + from jarabe.model import shell + from jarabe.view.viewsource import setup_view_source + from jarabe.journal import misc +@@ -253,3 +256,44 @@ def __popup_cb(self, palette): + self._progress_bar.props.fraction = fraction + self._free_space_label.props.label = _('%(free_space)d MB Free') % \ + {'free_space': free_space / (1024 * 1024)} ++ ++ ++class JournalVolumePalette(VolumePalette): ++ ++ __gtype_name__ = 'JournalVolumePalette' ++ ++ def __init__(self, mount): ++ VolumePalette.__init__(self, mount) ++ ++ journal_separator = gtk.SeparatorMenuItem() ++ journal_separator.show() ++ ++ self.menu.prepend(journal_separator) ++ ++ icon = Icon(icon_name='transfer-from', icon_size=gtk.ICON_SIZE_MENU) ++ icon.show() ++ ++ menu_item_journal_restore = MenuItem(_('Restore Journal')) ++ menu_item_journal_restore.set_image(icon) ++ menu_item_journal_restore.connect('activate', self.__journal_restore_activate_cb, mount.get_root().get_path()) ++ menu_item_journal_restore.show() ++ ++ self.menu.prepend(menu_item_journal_restore) ++ ++ icon = Icon(icon_name='transfer-to', icon_size=gtk.ICON_SIZE_MENU) ++ icon.show() ++ ++ menu_item_journal_backup = MenuItem(_('Backup Journal')) ++ menu_item_journal_backup.set_image(icon) ++ menu_item_journal_backup.connect('activate', self.__journal_backup_activate_cb, mount.get_root().get_path()) ++ menu_item_journal_backup.show() ++ ++ self.menu.prepend(menu_item_journal_backup) ++ ++ def __journal_backup_activate_cb(self, menu_item, mount_path): ++ dialog = VolumeBackupDialog(mount_path) ++ dialog.show() ++ ++ def __journal_restore_activate_cb(self, menu_item, mount_path): ++ dialog = VolumeRestoreDialog(mount_path) ++ dialog.show() +-- +1.7.6 + |