From 58b53633d99eb739c84e123f418e41b1a4048f90 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Fri, 27 Jul 2007 16:19:36 +0000 Subject: Add temporary "Backup" button, for Trial-2 --- diff --git a/NEWS b/NEWS index d4579f5..ad17e63 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,5 @@ +* Added temporary backup capability for Trial-2 (danw) + 35 * Fix resume icon. (tomeu) diff --git a/backup.py b/backup.py new file mode 100644 index 0000000..2dee6a4 --- /dev/null +++ b/backup.py @@ -0,0 +1,165 @@ +# Copyright (C) 2007 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import gobject +import gtk +import sugar.env +import sugar.profile + +import os +import popen2 +import re +import signal +import sys +from threading import Thread + +def start_backup(verbose): + backup_info = sugar.profile.get_trial2_backup() + if not backup_info: + raise RuntimeError("Backup key not defined in Sugar profile") + + match = re.match(r'^([^@]*)@([^:]*):(.*)$', backup_info) + if not match: + raise RuntimeError("Backup key '%s' is not user@host:path" % backup_info) + + remote_user = match.group(1) + server = match.group(2) + remote_path = match.group(3) + + if sugar.env.is_emulator(): + local_path = sugar.env.get_profile_path() + else: + local_path = os.path.expanduser('~') + + privkey = sugar.env.get_profile_path('owner.key') + + ssh_cmd = '/usr/bin/ssh -F /dev/null -o "PasswordAuthentication no" -i "%s" -l "%s"' % (privkey, remote_user) + rsync_cmd = '/usr/bin/rsync -az%s -e \'%s\' %s "%s:%s"' % (verbose and 'P' or '', ssh_cmd, local_path, server, remote_path) + + pipe = popen2.Popen3(rsync_cmd, True) + if pipe.poll() != -1: + raise RuntimeError('rsync failed: %s' % pipe.childerr.read()) + + return pipe + + +class BackupThread(Thread): + def __init__(self, progress_cb, done_cb): + self._progress_cb = progress_cb + self._done_cb = done_cb + self._errmsg = None + self._pipe = start_backup(True) + + Thread.__init__(self, target=self._backup_thread) + + def _backup_thread(self): + for line in self._pipe.fromchild: + # After each file, rsync prints a line something like: + # 9350 100% 4.46MB/s 0:00:00 (xfer#9, to-check=7719/7769) + match = re.match(r'.*to-check=(\d+)/(\d+)', line) + if match: + try: + remaining = int(match.group(1)) + total = int(match.group(2)) + progress = (total - remaining) * 100 / total + gobject.idle_add(self._progress_cb, progress) + except: + pass + + if self._pipe.poll() != 0: + self._errmsg = self._pipe.childerr.read() + gobject.idle_add(self._done_cb) + + def errmsg(self): + return self._errmsg + + def kill(self): + if self._pipe.poll() == -1: + os.kill(self._pipe.pid, signal.SIGINT) + +class BackupDialog(gtk.Dialog): + def __init__(self): + gtk.Dialog.__init__(self, flags=gtk.DIALOG_MODAL) + self.set_title('Backup') + self.set_has_separator(False) + + label = gtk.Label('Backing up data to school server...') + self.vbox.pack_start(label) + + self._progress_bar = gtk.ProgressBar() + self.vbox.pack_start(self._progress_bar) + + self.vbox.show_all() + + self.add_button(gtk.STOCK_STOP, gtk.RESPONSE_CLOSE) + + self._thread = BackupThread(self._progress_cb, self._done_cb) + self._thread.start() + self._timeout = gobject.timeout_add(100, self._timeout_cb) + + self.connect('response', self._response_cb) + + def _timeout_cb(self): + self._progress_bar.pulse() + return True + + def _progress_cb(self, percent): + self._progress_bar.set_fraction(percent / 100.0) + if self._timeout: + gobject.source_remove(self._timeout) + self._timeout = None + return False + + def _done_cb(self): + self.response(gtk.RESPONSE_CLOSE) + + def _response_cb(self, widget, response): + if self._timeout: + gobject.source_remove(self._timeout) + + if self._thread.errmsg(): + dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, + "Backup failed:\n%s" % self._thread.errmsg()) + dlg.run() + elif self._thread.isAlive(): + self._thread.kill() + + self.destroy() + +def backup_gui(): + try: + BackupDialog().run() + except RuntimeError, e: + dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, \ + gtk.BUTTONS_OK, 'Backup failed: %s' % str(e)) + dlg.run() + +def backup_cron(): + try: + pipe = start_backup(False) + sys.exit(pipe.wait()) + except RuntimeError, e: + sys.stderr.write("Backup failed: %s\n" % str(e)) + sys.exit(1) + +if __name__ == "__main__": + if os.environ.has_key('DISPLAY'): + gobject.threads_init() + backup_gui() + else: + backup_cron() diff --git a/journaltoolbox.py b/journaltoolbox.py index 7237fa4..dbf10c5 100644 --- a/journaltoolbox.py +++ b/journaltoolbox.py @@ -22,6 +22,8 @@ from datetime import datetime, timedelta import gobject import gtk +import backup + from sugar.graphics import units from sugar.graphics import color from sugar.graphics.xocolor import XoColor @@ -45,6 +47,10 @@ class JournalToolbox(Toolbox): self.add_toolbar(_('Entry'), self.entry_toolbar) self.entry_toolbar.show() + self.backup_toolbar = BackupToolbar() + self.add_toolbar(_('Backup'), self.backup_toolbar) + self.backup_toolbar.show() + #self.create_toolbar = CreateToolbar() #self.add_toolbar(_('Create'), self.create_toolbar) #self.create_toolbar.show() @@ -342,3 +348,17 @@ class EntryToolbar(gtk.Toolbar): self._copy.props.sensitive = False self._resume.props.sensitive = False +class BackupToolbar(gtk.Toolbar): + __gtype_name__ = 'BackupToolbar' + + def __init__(self): + gtk.Toolbar.__init__(self) + + self._backup = ToolButton('document-save') + self._backup.set_tooltip(_('Backup')) + self._backup.connect('clicked', self._backup_clicked_cb) + self.insert(self._backup, -1) + self._backup.show() + + def _backup_clicked_cb(self, button): + backup.backup_gui() -- cgit v0.9.1