[Sugar-devel] [PATCH] Journal Volumes Backup and Restore
Martin Langhoff
martin.langhoff at gmail.com
Mon Jun 28 15:11:04 EDT 2010
Hi Martin,
great to see action on this front. Thanks for working on this.
I have a few questions
- Why is the UI in the main Journal UI? A manual backup should be a
very infrequent op. (And same with a "full" restore).
- Is the 'restore' option a 'full restore' or does it allow to browse
existing backup snapshots?
...If it is a 'full restore', is it destructive? What does it do with
documents that already exist but have been modified locally? What does
it do with space concerns (ie: if the backup won't fit on disk) ?
cheers,
m
On Mon, Jun 28, 2010 at 3:01 PM, Martin Abente
<mabente at paraguayeduca.org> wrote:
> 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 | 56 ++++++++
> bin/journal-restore-volume | 65 +++++++++
> src/jarabe/journal/Makefile.am | 3 +-
> src/jarabe/journal/misc.py | 27 ++++
> src/jarabe/journal/processdialog.py | 233 +++++++++++++++++++++++++++++++++
> src/jarabe/journal/volumestoolbar.py | 5 +-
> src/jarabe/model/Makefile.am | 3 +-
> src/jarabe/model/processmanagement.py | 98 ++++++++++++++
> src/jarabe/view/palettes.py | 44 ++++++
> 10 files changed, 533 insertions(+), 5 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..8cc87b5 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..10bdba9
> --- /dev/null
> +++ b/bin/journal-backup-volume
> @@ -0,0 +1,56 @@
> +#!/usr/bin/env python
> +# Copyright (C) 2010, Paraguay Educa <tecnologia at 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()
> +
> +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..3150fca
> --- /dev/null
> +++ b/bin/journal-restore-volume
> @@ -0,0 +1,65 @@
> +#!/usr/bin/env python
> +# Copyright (C) 2010, Paraguay Educa <tecnologia at 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()
> +
> +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'))
> +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 f4bf273..a760869 100644
> --- a/src/jarabe/journal/Makefile.am
> +++ b/src/jarabe/journal/Makefile.am
> @@ -14,4 +14,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 24ad216..6e3cb95 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 at 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
> @@ -249,3 +250,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..05bc14b
> --- /dev/null
> +++ b/src/jarabe/journal/processdialog.py
> @@ -0,0 +1,233 @@
> +#!/usr/bin/env python
> +# Copyright (C) 2010, Plan Ceibal <comunidad at plan.ceibal.edu.uy>
> +# Copyright (C) 2010, Paraguay Educa <tecnologia at 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):
> +
> + #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._restart_after = False
> + self._process_script = processmanagement.find_and_absolutize(process_script)
> + self._process_params = process_params
> + 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.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.show()
> +
> + self._vbox.pack_start(self._message, True)
> +
> + def _setup_options(self):
> + hbox = gtk.HBox(True, 3)
> + hbox.show()
> +
> + self._start_button = gtk.Button()
> + self._start_button.set_label(_('Start'))
> + self._start_button.connect('clicked', self.__start_cb)
> + self._start_button.show()
> +
> + self._close_button = gtk.Button()
> + self._close_button.set_label(_('Close'))
> + self._close_button.connect('clicked', self.__close_cb)
> + self._close_button.show()
> +
> + self._restart_button = gtk.Button()
> + 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_text(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_text(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_text('%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):
> + process_script = 'journal-backup-volume'
> + process_params = [volume_path, misc.get_backup_identifier()]
> +
> + ProcessDialog.__init__(self, process_script, process_params)
> + self._resetup_information(volume_path)
> +
> + def _resetup_information(self, volume_path):
> + self._start_message = _('Please wait, saving journal content to %s.') % volume_path
> + self._finished_message = _('The journal content has been saved.')
> +
> + self._title.set_markup('<b>%s</b>' % _('Backup'))
> + self._message.set_text(_('Journal content will be saved to %s') % volume_path)
> +
> +class VolumeRestoreDialog(ProcessDialog):
> +
> + def __init__(self, volume_path):
> + process_script = 'journal-restore-volume'
> + process_params = [volume_path, misc.get_backup_identifier()]
> +
> + ProcessDialog.__init__(self, process_script, process_params)
> + self._resetup_information(volume_path)
> + self._restart_after = True
> +
> + def _resetup_information(self, volume_path):
> + self._start_message = _('Please wait, restoring journal content from %s') % volume_path
> + self._finished_message = _('The journal content has been restored.')
> +
> + self._title.set_markup('<b>%s</b>' % _('Restore'))
> + self._message.set_text(_('Journal content will be restored from %s') % volume_path)
> +
> diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py
> index 74b974c..2e64fe2 100644
> --- a/src/jarabe/journal/volumestoolbar.py
> +++ b/src/jarabe/journal/volumestoolbar.py
> @@ -27,7 +27,7 @@ from sugar.graphics.palette import Palette
> from sugar.graphics.xocolor import XoColor
>
> from jarabe.journal import model
> -from jarabe.view.palettes import VolumePalette
> +from jarabe.view.palettes import JournalVolumePalette
>
> class VolumesToolbar(gtk.Toolbar):
> __gtype_name__ = 'VolumesToolbar'
> @@ -164,11 +164,12 @@ 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
>
> +
> class JournalButton(BaseButton):
> def __init__(self):
> BaseButton.__init__(self, mount_point='/')
> diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am
> index e9f0700..8fdc552 100644
> --- a/src/jarabe/model/Makefile.am
> +++ b/src/jarabe/model/Makefile.am
> @@ -15,4 +15,5 @@ sugar_PYTHON = \
> shell.py \
> screen.py \
> session.py \
> - sound.py
> + sound.py \
> + processmanagement.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 at paraguayeduca.org>
> +# Copyright (C) 2010, Plan Ceibal <comunidad at 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 ad84f08..2fc4d5f 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 at plan.ceibal.edu.uy>
> +# Copyright (C) 2010, Paraguay Educa <tecnologia at 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 import activityfactory
> from sugar.activity.activityhandle import ActivityHandle
>
> +from jarabe.journal.processdialog import VolumeBackupDialog, VolumeRestoreDialog
> from jarabe.model import shell
> from jarabe.view import launcher
> from jarabe.view.viewsource import setup_view_source
> @@ -258,3 +261,44 @@ class VolumePalette(Palette):
> 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.6.0.4
>
> _______________________________________________
> Sugar-devel mailing list
> Sugar-devel at lists.sugarlabs.org
> http://lists.sugarlabs.org/listinfo/sugar-devel
>
--
martin.langhoff at gmail.com
martin at laptop.org -- School Server Architect
- ask interesting questions
- don't get distracted with shiny stuff - working code first
- http://wiki.laptop.org/go/User:Martinlanghoff
More information about the Sugar-devel
mailing list