[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