Backup dialog.

This commit is contained in:
Agnieszka Kostrzewa 2012-02-20 07:56:38 +01:00
parent 30bae277a1
commit df02ed2aa4
10 changed files with 315 additions and 78 deletions

View File

@ -19,7 +19,7 @@
<property name="options"> <property name="options">
<set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set> <set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set>
</property> </property>
<widget class="QWizardPage" name="wizardPage1"> <widget class="QWizardPage" name="select_vms_page">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_4">
@ -39,7 +39,7 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWizardPage" name="wizardPage"> <widget class="QWizardPage" name="select_dir_page">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
@ -55,7 +55,7 @@
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="comboBox"> <widget class="QComboBox" name="dev_combobox">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -92,10 +92,10 @@
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="pathLineEdit"/> <widget class="QLineEdit" name="dir_line_edit"/>
</item> </item>
<item row="1" column="2"> <item row="1" column="2">
<widget class="QToolButton" name="selectPathButton"> <widget class="QToolButton" name="select_path_button">
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
@ -106,7 +106,7 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWizardPage" name="wizardPage2"> <widget class="QWizardPage" name="confirm_page">
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
@ -157,7 +157,7 @@ p, li { white-space: pre-wrap; }
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWizardPage" name="wizardPage_2"> <widget class="QWizardPage" name="commit_page">
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item> <item>
<widget class="QLabel" name="label_8"> <widget class="QLabel" name="label_8">
@ -176,7 +176,7 @@ p, li { white-space: pre-wrap; }
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QProgressBar" name="progressBar"> <widget class="QProgressBar" name="progress_bar">
<property name="value"> <property name="value">
<number>24</number> <number>24</number>
</property> </property>

BIN
icons/mount.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
icons/redfirewall2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -39,9 +39,10 @@ from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, Pro
import subprocess import subprocess
import time import time
import threading
from operator import itemgetter from operator import itemgetter
from thread_monitor import *
from multiselectwidget import * from multiselectwidget import *
whitelisted_filename = 'whitelisted-appmenus.list' whitelisted_filename = 'whitelisted-appmenus.list'
@ -51,22 +52,6 @@ class AppListWidgetItem(QListWidgetItem):
super(AppListWidgetItem, self).__init__(name, parent) super(AppListWidgetItem, self).__init__(name, parent)
self.filename = filename self.filename = filename
class ThreadMonitor(QObject):
def __init__(self):
self.success = True
self.error_msg = None
self.event_finished = threading.Event()
def set_error_msg(self, error_msg):
self.success = False
self.error_msg = error_msg
self.set_finished()
def is_finished(self):
return self.event_finished.is_set()
def set_finished(self):
self.event_finished.set()
class AppmenuSelectManager: class AppmenuSelectManager:

View File

@ -30,6 +30,7 @@ from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesException from qubes.qubes import QubesException
from qubes.qubes import QubesDaemonPidfile from qubes.qubes import QubesDaemonPidfile
from qubes.qubes import QubesHost from qubes.qubes import QubesHost
from qubes import qubesutils
import qubesmanager.resources_rc import qubesmanager.resources_rc
@ -37,9 +38,12 @@ from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, Pro
import subprocess import subprocess
import time import time
import threading from thread_monitor import *
from operator import itemgetter from operator import itemgetter
from datetime import datetime
from string import replace
from ui_backupdlg import * from ui_backupdlg import *
from multiselectwidget import * from multiselectwidget import *
@ -47,40 +51,255 @@ from multiselectwidget import *
class BackupVMsWindow(Ui_Backup, QWizard): class BackupVMsWindow(Ui_Backup, QWizard):
def __init__(self, parent=None): __pyqtSignals__ = ("backup_progress(int)",)
excluded = []
to_backup = []
def __init__(self, app, qvm_collection, blk_manager, parent=None):
super(BackupVMsWindow, self).__init__(parent) super(BackupVMsWindow, self).__init__(parent)
self.app = app
self.qvm_collection = qvm_collection
self.blk_manager = blk_manager
self.backup_dir = None
self.func_output = []
for vm in self.qvm_collection.values():
if vm.qid == 0:
self.vm = vm
break;
assert self.vm != None
self.setupUi(self) self.setupUi(self)
self.selectVMsWidget = MultiSelectWidget(self) self.dir_line_edit.setReadOnly(True)
self.verticalLayout.insertWidget(1, self.selectVMsWidget)
self.select_vms_widget = MultiSelectWidget(self)
self.verticalLayout.insertWidget(1, self.select_vms_widget)
self.selectVMsWidget.available_list.addItem("netVM1")
self.selectVMsWidget.available_list.addItem("appVM1")
self.selectVMsWidget.available_list.addItem("appVM2")
self.selectVMsWidget.available_list.addItem("templateVM1")
self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed) self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
self.connect(self.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated)
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
self.select_vms_page.isComplete = self.has_selected_vms
self.select_dir_page.isComplete = self.has_selected_dir
#FIXME
#this causes to run isComplete() twice, I don't know why
self.select_vms_page.connect(self.select_vms_widget, SIGNAL("selected_changed()"), SIGNAL("completeChanged()"))
self.__fill_vms_list__()
def reject(self): self.__fill_devs_list__()
self.done(0)
def save_and_apply(self): def __fill_vms_list__(self):
pass for vm in self.qvm_collection.values():
if vm.is_running() and vm.qid != 0:
@pyqtSlot(name='on_selectPathButton_clicked') self.excluded.append(vm.name)
def selectPathButton_clicked(self): continue
self.path = self.pathLineEdit.text()
newPath = QFileDialog.getExistingDirectory(self, 'Select backup directory.')
if newPath:
self.pathLineEdit.setText(newPath)
self.path = newPath
def current_page_changed(self, id):
self.button(self.CancelButton).setDisabled(id==3)
if vm.is_appvm() and vm.internal:
self.excluded.append(vm.name)
continue
if vm.is_template() and vm.installed_by_rpm:
self.excluded.append(vm.name)
continue
self.to_backup.append(vm.name)
self.select_vms_widget.available_list.addItem(vm.name)
def __fill_devs_list__(self):
self.dev_combobox.clear()
self.dev_combobox.addItem("None")
for a in self.blk_manager.attached_devs:
if self.blk_manager.attached_devs[a]['attached_to']['vm'] == self.vm.name :
att = a + " " + unicode(self.blk_manager.attached_devs[a]['size']) + " " + self.blk_manager.attached_devs[a]['desc']
self.dev_combobox.addItem(att, QVariant(a))
for a in self.blk_manager.free_devs:
att = a + " " + unicode(self.blk_manager.free_devs[a]['size']) + " " + self.blk_manager.free_devs[a]['desc']
self.dev_combobox.addItem(att, QVariant(a))
self.dev_combobox.setCurrentIndex(0) #current selected is null ""
self.prev_dev_idx = 0
self.dir_line_edit.clear()
self.dir_line_edit.setEnabled(False)
self.select_path_button.setEnabled(False)
def __check_if_mounted__(self, dev_path):
mounts_file = open("/proc/mounts")
for m in list(mounts_file):
if m.startswith(dev_path):
print m
return m.split(" ")[1]
return None
def __mount_device__(self, dev_path):
try:
mount_dir_name = "backup" + replace(str(datetime.now()),' ', '-').split(".")[0]
pmount_cmd = ["pmount", dev_path, mount_dir_name]
res = subprocess.check_call(pmount_cmd)
print "pmount device res: ", res
except Exception as ex:
QMessageBox.warning (None, "Error mounting selected device!", "ERROR: {0}".format(ex))
return None
if res == 0:
self.dev_mount_path = "/media/"+mount_dir_name
return self.dev_mount_path
def __umount_device__(self, dev_mount_path):
try:
pumount_cmd = ["pumount", dev_mount_path]
res = subprocess.check_call(pumount_cmd)
print "pumount device res: ", res
except Exception as ex:
QMessageBox.warning (None, "Could not unmount backup device!", "ERROR: {0}".format(ex))
def __enable_dir_line_edit__(self, boolean):
self.dir_line_edit.setEnabled(boolean)
self.select_path_button.setEnabled(boolean)
def dev_combobox_activated(self, idx):
print self.dev_combobox.currentText()
if idx == self.prev_dev_idx: #nothing has changed
return
#there was a change
self.prev_dev_idx = idx
self.dir_line_edit.setText("")
self.backup_dir = None
self.dev_mount_path = None
self.__enable_dir_line_edit__(False)
if self.dev_combobox.currentText() != "None": #An existing device chosen
dev_name = str(self.dev_combobox.itemData(idx).toString())
if dev_name in self.blk_manager.free_devs:
if dev_name.startswith(self.vm.name): # originally attached to dom0
dev_path = "/dev/"+dev_name.split(":")[1]
print "device from dom0 - no need to attach"
else: # originally attached to another domain, eg. usbvm
print "device from " + dev_name.split(":")[0]
#attach it to dom0, then treat it as an attached device
self.blk_manager.attach_device(self.vm, dev_name)
if dev_name in self.blk_manager.attached_devs: #is attached to dom0
print "device attached as " + self.blk_manager.attached_devs[dev_name]['attached_to']['frontend']
assert self.blk_manager.attached_devs[dev_name]['attached_to']['vm'] == self.vm.name
dev_path = "/dev/" + self.blk_manager.attached_devs[dev_name]['attached_to']['frontend']
#check if device mounted
self.dev_mount_path = self.__check_if_mounted__(dev_path)
if self.dev_mount_path != None:
self.__enable_dir_line_edit__(True)
else:
self.dev_mount_path = self.__mount_device__(dev_path)
if self.dev_mount_path != None:
self.__enable_dir_line_edit__(True)
self.select_dir_page.emit(SIGNAL("completeChanged()"))
@pyqtSlot(name='on_select_path_button_clicked')
def select_path_button_clicked(self):
self.backup_dir = self.dir_line_edit.text()
file_dialog = QFileDialog()
file_dialog.setReadOnly(True)
new_path = file_dialog.getExistingDirectory(self, "Select backup directory.", self.dev_mount_path)
if new_path:
self.dir_line_edit.setText(new_path)
self.backup_dir = new_path
self.select_dir_page.emit(SIGNAL("completeChanged()"))
def validateCurrentPage(self):
if self.currentPage() is self.select_vms_page:
for i in range(self.select_vms_widget.available_list.count()):
vmname = self.select_vms_widget.available_list.item(i).text()
self.excluded.append(vmname)
return True
def gather_output(self, s):
self.func_output.append(s)
def update_progress_bar(self, value):
print "progress bar value: ", value
self.emit(SIGNAL("backup_progress(int)"), value)
def __do_backup__(self, thread_monitor):
print "doiing backup"
msg = []
try:
qubesutils.backup_do(str(self.backup_dir), self.files_to_backup, self.update_progress_bar)
#simulate_long_lasting_proces(10, self.update_progress_bar)
except Exception as ex:
print "got exception from backup"
msg.append(str(ex))
if len(msg) > 0 :
thread_monitor.set_error_msg('\n'.join(msg))
thread_monitor.set_finished()
def current_page_changed(self, id):
if self.currentPage() is self.confirm_page:
del self.func_output[:]
self.files_to_backup = qubesutils.backup_prepare(str(self.backup_dir), exclude_list = self.excluded, print_callback = self.gather_output)
for i in self.excluded:
print i
self.textEdit.setReadOnly(True)
self.textEdit.setFontFamily("Monospace")
self.textEdit.setText("\n".join(self.func_output))
for i in self.func_output:
print i
for s in self.files_to_backup:
print s
elif self.currentPage() is self.commit_page:
self.button(self.CancelButton).setDisabled(True)
self.button(self.FinishButton).setDisabled(True)
print "butons disabled"
self.thread_monitor = ThreadMonitor()
thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,))
thread.daemon = True
print "will start thread"
thread.start()
while not self.thread_monitor.is_finished():
self.app.processEvents()
time.sleep (0.1)
if not self.thread_monitor.success:
QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg))
self.__umount_device__(self.dev_mount_path)
self.button(self.FinishButton).setEnabled(True)
def has_selected_vms(self):
print "isComplete called"
return self.select_vms_widget.selected_list.count() > 0
def has_selected_dir(self):
return self.backup_dir != None
def simulate_long_lasting_proces(period, progress_callback):
for i in range(period):
progress_callback((i*100)/period)
time.sleep(1)
progress_callback(100)
return 0
# Bases on the original code by: # Bases on the original code by:
# Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com> # Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
@ -113,7 +332,7 @@ def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setOrganizationName("The Qubes Project") app.setOrganizationName("The Qubes Project")
app.setOrganizationDomain("http://qubes-os.org") app.setOrganizationDomain("http://qubes-os.org")
app.setApplicationName("Qubes Restore VMs") app.setApplicationName("Qubes Backup VMs")
sys.excepthook = handle_exception sys.excepthook = handle_exception

View File

@ -42,12 +42,12 @@ from settings import VMSettingsWindow
from restore import RestoreVMsWindow from restore import RestoreVMsWindow
from backup import BackupVMsWindow from backup import BackupVMsWindow
from global_settings import GlobalSettingsWindow from global_settings import GlobalSettingsWindow
from thread_monitor import *
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
import subprocess import subprocess
import time import time
import threading
from datetime import datetime,timedelta from datetime import datetime,timedelta
updates_stat_file = 'last_update.stat' updates_stat_file = 'last_update.stat'
@ -486,23 +486,6 @@ class VmShutdownMonitor(QObject):
else: else:
QTimer.singleShot (vm_shutdown_timeout, self.check_if_vm_has_shutdown) QTimer.singleShot (vm_shutdown_timeout, self.check_if_vm_has_shutdown)
class ThreadMonitor(QObject):
def __init__(self):
self.success = True
self.error_msg = None
self.event_finished = threading.Event()
def set_error_msg(self, error_msg):
self.success = False
self.error_msg = error_msg
self.set_finished()
def is_finished(self):
return self.event_finished.is_set()
def set_finished(self):
self.event_finished.set()
class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
row_height = 30 row_height = 30
@ -1105,7 +1088,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
@pyqtSlot(name='on_action_backup_triggered') @pyqtSlot(name='on_action_backup_triggered')
def action_backup_triggered(self): def action_backup_triggered(self):
backup_window = BackupVMsWindow() backup_window = BackupVMsWindow(app, self.qvm_collection, self.blk_manager)
backup_window.exec_() backup_window.exec_()
@ -1151,8 +1134,10 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
action = self.blk_menu.addAction(QIcon(":/remove.png"), str) action = self.blk_menu.addAction(QIcon(":/remove.png"), str)
action.setData(QVariant(d)) action.setData(QVariant(d))
if self.blk_menu.isEmpty() and len(self.blk_manager.free_devs) > 0: if len(self.blk_manager.free_devs) > 0:
for d in self.blk_manager.free_devs: for d in self.blk_manager.free_devs:
if d.startswith(vm.name):
continue
str = "Attach " + d + " " + unicode(self.blk_manager.free_devs[d]['size']) + " " + self.blk_manager.free_devs[d]['desc'] str = "Attach " + d + " " + unicode(self.blk_manager.free_devs[d]['size']) + " " + self.blk_manager.free_devs[d]['desc']
action = self.blk_menu.addAction(QIcon(":/add.png"), str) action = self.blk_menu.addAction(QIcon(":/add.png"), str)
action.setData(QVariant(d)) action.setData(QVariant(d))

View File

@ -5,6 +5,8 @@ from ui_multiselectwidget import *
class MultiSelectWidget(Ui_MultiSelectWidget, QWidget): class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
__pyqtSignals__ = ("selected_changed()",)
def __init__(self, parent=None): def __init__(self, parent=None):
super(MultiSelectWidget, self).__init__() super(MultiSelectWidget, self).__init__()
self.setupUi(self); self.setupUi(self);
@ -23,18 +25,20 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
item = src.takeItem(row) item = src.takeItem(row)
dst.addItem(item) dst.addItem(item)
dst.sortItems() dst.sortItems()
self.emit(SIGNAL("selected_changed()"))
def add_selected(self): def add_selected(self):
self.switch_selected(self.available_list, self.selected_list) self.switch_selected(self.available_list, self.selected_list)
def remove_selected(self): def remove_selected(self):
self.switch_selected(self.selected_list, self.available_list) self.switch_selected(self.selected_list, self.available_list)
def move_all(self, src, dst): def move_all(self, src, dst):
while src.count() > 0: while src.count() > 0:
item = src.takeItem(0) item = src.takeItem(0)
dst.addItem(item) dst.addItem(item)
dst.sortItems() dst.sortItems()
self.emit(SIGNAL("selected_changed()"))
def add_all(self): def add_all(self):
self.move_all(self.available_list, self.selected_list) self.move_all(self.available_list, self.selected_list)

View File

@ -145,7 +145,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
thread_monitor.set_finished() thread_monitor.set_finished()
return return
#self.fw_model.apply_rules() #self.fw_model.apply_rules()
#self.AppListManager.save_appmenu_select_changes() self.AppListManager.save_appmenu_select_changes()
thread_monitor.set_finished() thread_monitor.set_finished()
def current_tab_changed(self, idx): def current_tab_changed(self, idx):

View File

@ -0,0 +1,44 @@
#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2011 Marek Marczykowski <marmarek@mimuw.edu.pl>
#
# 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
from PyQt4.QtCore import *
import threading
class ThreadMonitor(QObject):
def __init__(self):
self.success = True
self.error_msg = None
self.event_finished = threading.Event()
def set_error_msg(self, error_msg):
self.success = False
self.error_msg = error_msg
self.set_finished()
def is_finished(self):
return self.event_finished.is_set()
def set_finished(self):
self.event_finished.set()

View File

@ -19,7 +19,7 @@
<property name="options"> <property name="options">
<set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set> <set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set>
</property> </property>
<widget class="QWizardPage" name="wizardPage1"> <widget class="QWizardPage" name="select_dir_page">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QGroupBox" name="groupBox_2"> <widget class="QGroupBox" name="groupBox_2">
@ -111,7 +111,7 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWizardPage" name="wizardPage"> <widget class="QWizardPage" name="select_vms_page">
<layout class="QVBoxLayout" name="verticalLayout_4"> <layout class="QVBoxLayout" name="verticalLayout_4">
<item> <item>
<widget class="QGroupBox" name="seletVMsGroupbox"> <widget class="QGroupBox" name="seletVMsGroupbox">
@ -184,7 +184,7 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWizardPage" name="wizardPage2"> <widget class="QWizardPage" name="confirm_page">
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
@ -235,7 +235,7 @@ p, li { white-space: pre-wrap; }
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWizardPage" name="wizardPage_2"> <widget class="QWizardPage" name="commit_page">
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item> <item>
<widget class="QLabel" name="label_8"> <widget class="QLabel" name="label_8">