Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAjay Garg <ajay@activitycentral.com>2012-10-13 16:18:52 (GMT)
committer Ajay Garg <ajay@activitycentral.com>2012-10-15 15:48:23 (GMT)
commit81ada148b643f50d645a7ad650608430c1f494cf (patch)
treeb496f98e8653199f6f4d9e3296392eaa7bdb99bd
parent1fd85e51a40faff942221a9cfde5c4f38152a307 (diff)
Backup/Restore journal to/from volume(s).
Signed-off-by: Ajay Garg <ajay@activitycentral.com>
-rw-r--r--bin/Makefile.am4
-rw-r--r--bin/journal-backup-volume57
-rw-r--r--bin/journal-restore-volume67
-rw-r--r--src/jarabe/journal/Makefile.am1
-rw-r--r--src/jarabe/journal/misc.py16
-rw-r--r--src/jarabe/journal/processdialog.py263
-rw-r--r--src/jarabe/journal/volumestoolbar.py4
-rw-r--r--src/jarabe/model/Makefile.am1
-rw-r--r--src/jarabe/model/processmanagement.py120
-rw-r--r--src/jarabe/view/palettes.py46
10 files changed, 574 insertions, 5 deletions
diff --git a/bin/Makefile.am b/bin/Makefile.am
index 845816c..df53e04 100644
--- a/bin/Makefile.am
+++ b/bin/Makefile.am
@@ -3,7 +3,9 @@ python_scripts = \
sugar-emulator \
sugar-install-bundle \
sugar-launch \
- sugar-session
+ sugar-session \
+ 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..f3ad6d8
--- /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 98effcf..df8f961 100644
--- a/src/jarabe/journal/Makefile.am
+++ b/src/jarabe/journal/Makefile.am
@@ -15,5 +15,6 @@ sugar_PYTHON = \
model.py \
objectchooser.py \
palettes.py \
+ processdialog.py \
volumestoolbar.py \
webdavmanager.py
diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py
index 877c1ab..f627c1b 100644
--- a/src/jarabe/journal/misc.py
+++ b/src/jarabe/journal/misc.py
@@ -40,6 +40,7 @@ from jarabe.journal.journalentrybundle import JournalEntryBundle
from jarabe.journal import model
from jarabe.journal import journalwindow
+_NOT_AVAILABLE = _('Not available')
def _get_icon_for_mime(mime_type):
generic_types = mime.get_all_generic_types()
@@ -318,7 +319,6 @@ def get_xo_serial():
_OFW_TREE = '/ofw'
_PROC_TREE = '/proc/device-tree'
_SN = 'serial-number'
- _not_available = _('Not available')
serial_no = None
if os.path.exists(os.path.join(_OFW_TREE, _SN)):
@@ -327,7 +327,7 @@ def get_xo_serial():
serial_no = read_file(os.path.join(_PROC_TREE, _SN))
if serial_no is None:
- serial_no = _not_available
+ serial_no = _NOT_AVAILABLE
# Remove the trailing binary character, else DBUS will crash.
return serial_no.rstrip('\x00')
@@ -346,3 +346,15 @@ def read_file(path):
else:
logging.debug('No information in file or directory: %s', path)
return None
+
+
+def get_nick():
+ client = GConf.Client.get_default()
+ return client.get_string("/desktop/sugar/user/nick")
+
+
+def get_backup_identifier():
+ serial_number = get_xo_serial()
+ if serial_number is _NOT_AVAILABLE:
+ serial_number = get_nick()
+ return serial_number
diff --git a/src/jarabe/journal/processdialog.py b/src/jarabe/journal/processdialog.py
new file mode 100644
index 0000000..d738303
--- /dev/null
+++ b/src/jarabe/journal/processdialog.py
@@ -0,0 +1,263 @@
+#!/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/>.
+
+
+from gi.repository import GObject
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GConf
+
+import logging
+
+from gettext import gettext as _
+from sugar3.graphics import style
+from sugar3.graphics.icon import Icon
+from sugar3.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._prerequisite_message = ('Prerequisites were not met')
+
+ self.set_border_width(style.LINE_WIDTH)
+ width = Gdk.Screen.width()
+ height = Gdk.Screen.height()
+ self.set_size_request(width, height)
+ self.set_position(Gtk.WindowPosition.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.StateType.NORMAL, Gdk.Color.parse("white")[1])
+ 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, False, 0)
+
+ self._title = Gtk.Label()
+ self._title.modify_fg(Gtk.StateType.NORMAL, style.COLOR_BLACK.get_gdk_color())
+ self._title.set_use_markup(True)
+ self._title.set_justify(Gtk.Justification.CENTER)
+ self._title.show()
+
+ self._vbox.pack_start(self._title, False, False, 0)
+
+ self._message = Gtk.Label()
+ self._message.modify_fg(Gtk.StateType.NORMAL, style.COLOR_BLACK.get_gdk_color())
+ self._message.set_use_markup(True)
+ self._message.set_line_wrap(True)
+ self._message.set_justify(Gtk.Justification.CENTER)
+ self._message.show()
+
+ self._vbox.pack_start(self._message, True, True, 0)
+
+ 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(_('Close'))
+ 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(xalign=1, yalign=0, xscale=0, yscale=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()
+ self._progress_bar.hide()
+
+ alignment.add(self._progress_bar)
+ self._vbox.pack_start(alignment, False, False, 0)
+
+ def __realize_cb(self, widget):
+ self.get_window().set_accept_focus(True)
+
+ def __close_cb(self, button):
+ self.destroy()
+
+ def __start_cb(self, button):
+ if self._check_prerequisites():
+ self._process_management.do_process([self._process_script] + self._process_params)
+ else:
+ self._set_status_failed(self, error_message=self._prerequisite_message)
+
+ def __restart_cb(self, button):
+ session_manager = get_session_manager()
+ session_manager.logout()
+
+ def _check_prerequisites(self):
+ return True
+
+ 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=None, 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()
+ self._restart_button.hide()
+
+ 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!')))
+
+ self._prerequisite_message = _(', please close all the running activities.')
+
+ def _check_prerequisites(self):
+ return len(shell.get_model()) <= 2
diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py
index ed2a0a3..1bf81bb 100644
--- a/src/jarabe/journal/volumestoolbar.py
+++ b/src/jarabe/journal/volumestoolbar.py
@@ -37,7 +37,7 @@ from sugar3.graphics.xocolor import XoColor
from sugar3 import env
from jarabe.journal import model
-from jarabe.view.palettes import VolumePalette, RemoteSharePalette
+from jarabe.view.palettes import JournalVolumePalette, RemoteSharePalette
_JOURNAL_0_METADATA_DIR = '.olpc.store'
@@ -445,7 +445,7 @@ class VolumeButton(BaseButton):
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 2fc6b1c..d40fb8d 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..cb429f6
--- /dev/null
+++ b/src/jarabe/model/processmanagement.py
@@ -0,0 +1,120 @@
+# 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
+
+from gi.repository import GObject
+from gi.repository import Gio
+
+import os
+import glib
+
+
+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.SignalFlags.RUN_FIRST, None, ([str])),
+ 'process-management-started' : (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ 'process-management-finished' : (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ 'process-management-failed' : (GObject.SignalFlags.RUN_FIRST, 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, user_data=None):
+ data = stream.read_finish(result)
+
+ if data != 0:
+ self.emit('process-management-running', data)
+ stream.read_async([],
+ BYTES_TO_READ,
+ GObject.PRIORITY_LOW,
+ None,
+ self._report_process_status,
+ None)
+
+ def _report_process_error(self, stream, result, concat_err=''):
+ data = stream.read_finish(result)
+ concat_err = concat_err + data
+
+ if data != 0:
+ self.emit('process-management-failed', concat_err)
+ else:
+ stream.read_async([],
+ BYTES_TO_READ,
+ GObject.PRIORITY_LOW,
+ None,
+ self._report_process_error,
+ concat_err)
+
+ def _notify_error(self, stderr):
+ stdin_stream = Gio.UnixInputStream(fd=stderr, close_fd=True)
+ stdin_stream.read_async([],
+ BYTES_TO_READ,
+ GObject.PRIORITY_LOW,
+ None,
+ self._report_process_error,
+ None)
+
+ def _notify_process_status(self, stdout):
+ stdin_stream = Gio.UnixInputStream(fd=stdout, close_fd=True)
+ stdin_stream.read_async([],
+ BYTES_TO_READ,
+ GObject.PRIORITY_LOW,
+ None,
+ self._report_process_status,
+ None)
+
+ 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 4ccf8bc..bbbf822 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
@@ -33,6 +35,7 @@ from sugar3.graphics import style
from sugar3.graphics.xocolor import XoColor
from sugar3.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
@@ -319,3 +322,46 @@ class RemoteSharePalette(Palette):
def __popup_cb(self, palette):
pass
+
+
+
+
+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()