Merge branch 'new-backups'
This commit is contained in:
commit
fda7180d16
104
backupdlg.ui
104
backupdlg.ui
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>700</width>
|
||||
<width>650</width>
|
||||
<height>399</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -148,21 +148,21 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="select_dir_page">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Backup destination directory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Device:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="1" column="2">
|
||||
<widget class="QComboBox" name="dev_combobox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
@ -192,23 +192,93 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Backup directory:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="4" column="2">
|
||||
<widget class="QLineEdit" name="dir_line_edit"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<item row="4" column="3">
|
||||
<widget class="QToolButton" name="select_path_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QComboBox" name="appvm_combobox"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Target AppVM:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Backup directory or VM command:</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Backup security</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Encryption / Verification<br/>passphrase:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Encrypt backup:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="encryption_checkbox">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Reenter passphrase:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="passphrase_line_edit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="passphrase_line_edit_verify">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -238,8 +308,8 @@
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html></string>
|
||||
</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;"><br /></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -264,7 +334,7 @@ p, li { white-space: pre-wrap; }
|
||||
<widget class="QWizardPage" name="commit_page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_8">
|
||||
<widget class="QLabel" name="progress_status">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
|
@ -31,6 +31,7 @@ from qubes.qubes import QubesVmCollection
|
||||
from qubes.qubes import QubesException
|
||||
from qubes.qubes import QubesDaemonPidfile
|
||||
from qubes.qubes import QubesHost
|
||||
from qubes import backup
|
||||
from qubes import qubesutils
|
||||
|
||||
import qubesmanager.resources_rc
|
||||
@ -65,21 +66,22 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
self.shutdown_vm_func = shutdown_vm_func
|
||||
|
||||
self.dev_mount_path = None
|
||||
self.backup_dir = None
|
||||
self.backup_location = None
|
||||
self.func_output = []
|
||||
self.excluded = []
|
||||
self.selected_vms = []
|
||||
|
||||
for vm in self.qvm_collection.values():
|
||||
if vm.qid == 0:
|
||||
self.vm = vm
|
||||
break;
|
||||
|
||||
|
||||
assert self.vm != None
|
||||
|
||||
self.setupUi(self)
|
||||
|
||||
self.progress_status.text = "Backup in progress..."
|
||||
self.show_running_vms_warning(False)
|
||||
self.dir_line_edit.setReadOnly(True)
|
||||
self.dir_line_edit.setReadOnly(False)
|
||||
|
||||
self.select_vms_widget = MultiSelectWidget(self)
|
||||
self.verticalLayout.insertWidget(1, self.select_vms_widget)
|
||||
@ -92,17 +94,31 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
self.shutdown_running_vms_button.clicked.connect(self.shutdown_all_running_selected)
|
||||
self.connect(self.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated)
|
||||
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
|
||||
self.dir_line_edit.connect(self.dir_line_edit, SIGNAL("textChanged(QString)"), self.backup_location_changed)
|
||||
|
||||
self.select_vms_page.isComplete = self.has_selected_vms
|
||||
self.select_dir_page.isComplete = self.has_selected_dir
|
||||
self.select_dir_page.isComplete = self.has_selected_dir_and_pass
|
||||
#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.select_vms_page.connect(
|
||||
self.select_vms_widget,
|
||||
SIGNAL("selected_changed()"),
|
||||
SIGNAL("completeChanged()"))
|
||||
self.passphrase_line_edit.connect(
|
||||
self.passphrase_line_edit,
|
||||
SIGNAL("textChanged(QString)"),
|
||||
self.backup_location_changed)
|
||||
self.passphrase_line_edit_verify.connect(
|
||||
self.passphrase_line_edit_verify,
|
||||
SIGNAL("textChanged(QString)"),
|
||||
self.backup_location_changed)
|
||||
|
||||
self.total_size = 0
|
||||
self.__fill_vms_list__()
|
||||
fill_devs_list(self)
|
||||
|
||||
fill_appvms_list(self)
|
||||
|
||||
def show_running_vms_warning(self, show):
|
||||
self.running_vms_warning.setVisible(show)
|
||||
self.shutdown_running_vms_button.setVisible(show)
|
||||
@ -114,11 +130,11 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
if vm.qid == 0:
|
||||
local_user = grp.getgrnam('qubes').gr_mem[0]
|
||||
home_dir = pwd.getpwnam(local_user).pw_dir
|
||||
self.size = qubesutils.get_disk_usage(home_dir)
|
||||
self.size = backup.get_disk_usage(home_dir)
|
||||
else:
|
||||
self.size = self.get_vm_size(vm)
|
||||
self.size = self.get_vm_size(vm)
|
||||
super(BackupVMsWindow.VmListItem, self).__init__(vm.name+ " (" + qubesutils.size_to_human(self.size) + ")")
|
||||
|
||||
|
||||
def get_vm_size(self, vm):
|
||||
size = 0
|
||||
if vm.private_img is not None:
|
||||
@ -167,7 +183,7 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
item.setForeground(QBrush(QColor(0, 0, 0)))
|
||||
|
||||
self.show_running_vms_warning(some_selected_vms_running)
|
||||
|
||||
|
||||
for i in range(self.select_vms_widget.available_list.count()):
|
||||
item = self.select_vms_widget.available_list.item(i)
|
||||
if item.vm.is_running() and item.vm.qid != 0:
|
||||
@ -193,7 +209,7 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
self.app.processEvents()
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
|
||||
|
||||
wait_time = 60.0
|
||||
for vm in vms:
|
||||
self.shutdown_vm_func(vm, wait_time*1000)
|
||||
@ -210,7 +226,6 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
|
||||
progress.hide()
|
||||
|
||||
|
||||
def get_running_vms(self):
|
||||
names = []
|
||||
vms = []
|
||||
@ -221,12 +236,8 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
vms.append(item.vm)
|
||||
return (names, vms)
|
||||
|
||||
|
||||
|
||||
|
||||
def dev_combobox_activated(self, idx):
|
||||
dev_combobox_activated(self, idx)
|
||||
|
||||
|
||||
@pyqtSlot(name='on_select_path_button_clicked')
|
||||
def select_path_button_clicked(self):
|
||||
@ -234,37 +245,51 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
|
||||
def validateCurrentPage(self):
|
||||
if self.currentPage() is self.select_vms_page:
|
||||
for i in range(self.select_vms_widget.selected_list.count()):
|
||||
if self.check_running() == True:
|
||||
QMessageBox.information(None, "Wait!", "Some selected VMs are running. Running VMs can not be backuped. Please shut them down or remove them from the list.")
|
||||
return False
|
||||
if self.check_running():
|
||||
QMessageBox.information(None, "Wait!", "Some selected VMs are running. Running VMs can not be backuped. Please shut them down or remove them from the list.")
|
||||
return False
|
||||
|
||||
self.selected_vms = []
|
||||
for i in range(self.select_vms_widget.selected_list.count()):
|
||||
self.selected_vms.append(self.select_vms_widget.selected_list.item(i).vm)
|
||||
|
||||
elif self.currentPage() is self.select_dir_page:
|
||||
if not self.backup_location:
|
||||
QMessageBox.information(None, "Wait!", "Enter backup target location first.")
|
||||
return False
|
||||
if self.appvm_combobox.currentIndex() == 0 and \
|
||||
not os.path.isdir(self.backup_location):
|
||||
QMessageBox.information(None, "Wait!",
|
||||
"Selected directory do not exists or not a directory (%s)." % self.backup_location)
|
||||
return False
|
||||
if not len(self.passphrase_line_edit.text()):
|
||||
QMessageBox.information(None, "Wait!", "Enter passphrase for backup encryption/verification first.")
|
||||
return False
|
||||
if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text():
|
||||
QMessageBox.information(None, "Wait!", "Enter the same passphrase in both fields.")
|
||||
return False
|
||||
|
||||
del self.excluded[:]
|
||||
for i in range(self.select_vms_widget.available_list.count()):
|
||||
vmname = self.select_vms_widget.available_list.item(i).vm.name
|
||||
self.excluded.append(vmname)
|
||||
|
||||
return True
|
||||
|
||||
def gather_output(self, s):
|
||||
self.func_output.append(s)
|
||||
|
||||
def update_progress_bar(self, value):
|
||||
if value == 100:
|
||||
self.emit(SIGNAL("backup_progress(int)"), value)
|
||||
|
||||
def check_backup_progress(self, initial_usage, total_backup_size):
|
||||
du = qubesutils.get_disk_usage(self.backup_dir)
|
||||
done = du - initial_usage
|
||||
percent = int((float(done)/total_backup_size)*100)
|
||||
return percent
|
||||
self.emit(SIGNAL("backup_progress(int)"), value)
|
||||
|
||||
def __do_backup__(self, thread_monitor):
|
||||
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)
|
||||
backup.backup_do(str(self.backup_location),
|
||||
self.files_to_backup,
|
||||
str(self.passphrase_line_edit.text()),
|
||||
progress_callback=self.update_progress_bar,
|
||||
encrypt=self.encryption_checkbox.isChecked(),
|
||||
appvm=self.target_appvm)
|
||||
#simulate_long_lasting_proces(10, self.update_progress_bar)
|
||||
except Exception as ex:
|
||||
print "Exception:",ex
|
||||
msg.append(str(ex))
|
||||
|
||||
if len(msg) > 0 :
|
||||
@ -272,14 +297,24 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
|
||||
thread_monitor.set_finished()
|
||||
|
||||
|
||||
|
||||
def current_page_changed(self, id):
|
||||
if self.currentPage() is self.confirm_page:
|
||||
|
||||
self.target_appvm = None
|
||||
if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
|
||||
self.target_appvm = self.qvm_collection.get_vm_by_name(
|
||||
self.appvm_combobox.currentText())
|
||||
|
||||
del self.func_output[:]
|
||||
try:
|
||||
self.files_to_backup = qubesutils.backup_prepare(str(self.backup_dir), exclude_list = self.excluded, print_callback = self.gather_output)
|
||||
self.files_to_backup = backup.backup_prepare(
|
||||
self.selected_vms,
|
||||
print_callback = self.gather_output,
|
||||
hide_vm_names=self.encryption_checkbox.isChecked())
|
||||
except Exception as ex:
|
||||
QMessageBox.critical(None, "Error while prepering backup.", "ERROR: {0}".format(ex))
|
||||
print "Exception:",ex
|
||||
QMessageBox.critical(None, "Error while preparing backup.", "ERROR: {0}".format(ex))
|
||||
|
||||
self.textEdit.setReadOnly(True)
|
||||
self.textEdit.setFontFamily("Monospace")
|
||||
@ -289,7 +324,6 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
self.button(self.FinishButton).setDisabled(True)
|
||||
self.button(self.CancelButton).setDisabled(True)
|
||||
self.thread_monitor = ThreadMonitor()
|
||||
initial_usage = qubesutils.get_disk_usage(self.backup_dir)
|
||||
thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
@ -299,52 +333,52 @@ class BackupVMsWindow(Ui_Backup, QWizard):
|
||||
while not self.thread_monitor.is_finished():
|
||||
self.app.processEvents()
|
||||
time.sleep (0.1)
|
||||
counter += 1
|
||||
if counter == 20:
|
||||
progress = self.check_backup_progress(initial_usage, self.total_size)
|
||||
self.progress_bar.setValue(progress)
|
||||
counter = 0
|
||||
|
||||
if not self.thread_monitor.success:
|
||||
QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg))
|
||||
|
||||
self.progress_status.setText = "Backup error."
|
||||
QMessageBox.warning (None, "Backup error!", "ERROR: {}".format(self.thread_monitor.error_msg))
|
||||
else:
|
||||
self.progress_bar.setValue(100)
|
||||
self.progress_status.setText = "Backup finished."
|
||||
if self.dev_mount_path != None:
|
||||
umount_device(self.dev_mount_path)
|
||||
self.button(self.FinishButton).setEnabled(True)
|
||||
|
||||
|
||||
|
||||
def reject(self):
|
||||
#cancell clicked while the backup is in progress.
|
||||
#calling kill on cp.
|
||||
#calling kill on tar.
|
||||
if self.currentPage() is self.commit_page:
|
||||
manager_pid = os.getpid()
|
||||
cp_pid_cmd = ["ps" ,"--ppid", str(manager_pid)]
|
||||
pid = None
|
||||
archive_pid_cmd = ["ps" ,"--ppid", str(manager_pid)]
|
||||
|
||||
while not self.thread_monitor.is_finished():
|
||||
cp_pid = subprocess.Popen(cp_pid_cmd, stdout = subprocess.PIPE)
|
||||
output = cp_pid.stdout.read().split("\n")
|
||||
|
||||
archive_pid = subprocess.Popen(archive_pid_cmd, stdout = subprocess.PIPE)
|
||||
output = archive_pid.stdout.readlines()
|
||||
|
||||
for l in output:
|
||||
if l.endswith("cp"):
|
||||
pid = l.split(" ")[1]
|
||||
break
|
||||
if pid != None:
|
||||
os.kill(int(pid), signal.SIGTERM)
|
||||
break
|
||||
if l.strip().endswith("tar"):
|
||||
os.kill(int(l.split(" ")[0]), signal.SIGTERM)
|
||||
time.sleep(0.1)
|
||||
|
||||
if self.dev_mount_path != None:
|
||||
umount_device(self.dev_mount_path)
|
||||
self.done(0)
|
||||
|
||||
|
||||
|
||||
def has_selected_vms(self):
|
||||
return self.select_vms_widget.selected_list.count() > 0
|
||||
|
||||
def has_selected_dir(self):
|
||||
return self.backup_dir != None
|
||||
|
||||
def has_selected_dir_and_pass(self):
|
||||
if not len(self.passphrase_line_edit.text()):
|
||||
return False
|
||||
if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text():
|
||||
return False
|
||||
return self.backup_location != None
|
||||
|
||||
def backup_location_changed(self, new_dir = None):
|
||||
self.backup_location = str(self.dir_line_edit.text())
|
||||
self.select_dir_page.emit(SIGNAL("completeChanged()"))
|
||||
|
||||
|
||||
# Bases on the original code by:
|
||||
|
@ -75,11 +75,25 @@ def umount_device(dev_mount_path):
|
||||
if button == QMessageBox.Ok:
|
||||
return dev_mount_path
|
||||
|
||||
def fill_appvms_list(dialog):
|
||||
dialog.appvm_combobox.clear()
|
||||
dialog.appvm_combobox.addItem("dom0")
|
||||
|
||||
dialog.appvm_combobox.setCurrentIndex(0) #current selected is null ""
|
||||
|
||||
for vm in dialog.qvm_collection.values():
|
||||
if vm.is_appvm() and vm.internal:
|
||||
continue
|
||||
if vm.is_template() and vm.installed_by_rpm:
|
||||
continue
|
||||
|
||||
if vm.is_running() and vm.qid != 0:
|
||||
dialog.appvm_combobox.addItem(vm.name)
|
||||
|
||||
def fill_devs_list(dialog):
|
||||
dialog.dev_combobox.clear()
|
||||
dialog.dev_combobox.addItem("None")
|
||||
|
||||
|
||||
dialog.blk_manager.blk_lock.acquire()
|
||||
for a in dialog.blk_manager.attached_devs:
|
||||
if dialog.blk_manager.attached_devs[a]['attached_to']['vm'] == dialog.vm.name :
|
||||
@ -98,7 +112,7 @@ def fill_devs_list(dialog):
|
||||
|
||||
def enable_dir_line_edit(dialog, boolean):
|
||||
dialog.dir_line_edit.setEnabled(boolean)
|
||||
dialog.select_path_button.setEnabled(boolean)
|
||||
dialog.select_path_button.setEnabled(boolean)
|
||||
|
||||
|
||||
def dev_combobox_activated(dialog, idx):
|
||||
@ -107,7 +121,7 @@ def dev_combobox_activated(dialog, idx):
|
||||
#there was a change
|
||||
|
||||
dialog.dir_line_edit.setText("")
|
||||
dialog.backup_dir = None
|
||||
dialog.backup_location = None
|
||||
|
||||
if dialog.dev_mount_path != None:
|
||||
dialog.dev_mount_path = umount_device(dialog.dev_mount_path)
|
||||
@ -115,7 +129,7 @@ def dev_combobox_activated(dialog, idx):
|
||||
dialog.dev_combobox.setCurrentIndex(dialog.prev_dev_idx)
|
||||
return
|
||||
|
||||
if dialog.dev_combobox.currentText() != "None": #An existing device chosen
|
||||
if dialog.dev_combobox.currentText() != "None": #An existing device chosen
|
||||
dev_name = str(dialog.dev_combobox.itemData(idx).toString())
|
||||
|
||||
dialog.blk_manager.blk_lock.acquire()
|
||||
@ -141,34 +155,43 @@ def dev_combobox_activated(dialog, idx):
|
||||
dialog.dev_combobox.setCurrentIndex(0) #if couldn't mount - set current device to "None"
|
||||
dialog.prev_dev_idx = 0
|
||||
return
|
||||
|
||||
|
||||
dialog.prev_dev_idx = idx
|
||||
|
||||
if dialog.dev_mount_path != None:
|
||||
# Initialize path with root of mounted device
|
||||
dialog.dir_line_edit.setText(dialog.dev_mount_path)
|
||||
dialog.backup_dir = dialog.dev_mount_path
|
||||
dialog.backup_location = dialog.dev_mount_path
|
||||
|
||||
dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
|
||||
|
||||
|
||||
def select_path_button_clicked(dialog):
|
||||
dialog.backup_dir = dialog.dir_line_edit.text()
|
||||
|
||||
def select_path_button_clicked(dialog, select_file = False):
|
||||
dialog.backup_location = dialog.dir_line_edit.text()
|
||||
file_dialog = QFileDialog()
|
||||
file_dialog.setReadOnly(True)
|
||||
|
||||
if dialog.dev_mount_path != None:
|
||||
new_path = file_dialog.getExistingDirectory(dialog, "Select backup directory.", dialog.dev_mount_path)
|
||||
if select_file:
|
||||
file_dialog_function = file_dialog.getOpenFileName
|
||||
else:
|
||||
new_path = file_dialog.getExistingDirectory(dialog, "Select backup directory.", "~")
|
||||
|
||||
if new_path:
|
||||
file_dialog_function = file_dialog.getExistingDirectory
|
||||
|
||||
new_appvm = None
|
||||
new_path = None
|
||||
if dialog.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
|
||||
new_appvm = str(dialog.appvm_combobox.currentText())
|
||||
elif dialog.dev_mount_path != None:
|
||||
new_path = file_dialog_function(dialog, "Select backup location.", dialog.dev_mount_path)
|
||||
else:
|
||||
new_path = file_dialog_function(dialog, "Select backup location.", "~")
|
||||
|
||||
if new_path != None:
|
||||
dialog.dir_line_edit.setText(new_path)
|
||||
dialog.backup_dir = new_path
|
||||
dialog.backup_location = new_path
|
||||
|
||||
if (new_path or new_appvm) and len(dialog.backup_location) > 0:
|
||||
dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
|
||||
|
||||
|
||||
|
||||
def simulate_long_lasting_proces(period, progress_callback):
|
||||
for i in range(period):
|
||||
progress_callback((i*100)/period)
|
||||
|
@ -30,6 +30,7 @@ from qubes.qubes import QubesVmCollection
|
||||
from qubes.qubes import QubesException
|
||||
from qubes.qubes import QubesDaemonPidfile
|
||||
from qubes.qubes import QubesHost
|
||||
from qubes.qubes import qubes_base_dir
|
||||
import qubesmanager.resources_rc
|
||||
|
||||
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
|
||||
@ -39,18 +40,19 @@ import time
|
||||
from operator import itemgetter
|
||||
from thread_monitor import *
|
||||
|
||||
from qubes import backup
|
||||
from qubes import qubesutils
|
||||
|
||||
from ui_restoredlg import *
|
||||
from multiselectwidget import *
|
||||
|
||||
from backup_utils import *
|
||||
|
||||
|
||||
from multiprocessing import Queue
|
||||
from multiprocessing.queues import Empty
|
||||
|
||||
class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
|
||||
__pyqtSignals__ = ("restore_progress(int)",)
|
||||
__pyqtSignals__ = ("restore_progress(int)","backup_progress(int)")
|
||||
|
||||
def __init__(self, app, qvm_collection, blk_manager, parent=None):
|
||||
super(RestoreVMsWindow, self).__init__(parent)
|
||||
@ -60,10 +62,11 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
self.blk_manager = blk_manager
|
||||
|
||||
self.dev_mount_path = None
|
||||
self.backup_dir = None
|
||||
self.backup_location = None
|
||||
self.restore_options = None
|
||||
self.backup_vms_list = None
|
||||
self.func_output = []
|
||||
self.feedback_queue = Queue()
|
||||
|
||||
self.excluded = {}
|
||||
|
||||
@ -71,7 +74,7 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
if vm.qid == 0:
|
||||
self.vm = vm
|
||||
break;
|
||||
|
||||
|
||||
assert self.vm != None
|
||||
|
||||
self.setupUi(self)
|
||||
@ -82,23 +85,26 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
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("restore_progress(QString)"), self.commit_text_edit.append)
|
||||
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
|
||||
|
||||
self.select_dir_page.isComplete = self.has_selected_dir
|
||||
self.select_vms_page.isComplete = self.has_selected_vms
|
||||
self.confirm_page.isComplete = self.all_vms_good
|
||||
#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.select_vms_page.connect(self.select_vms_widget, SIGNAL("selected_changed()"), SIGNAL("completeChanged()"))
|
||||
|
||||
fill_devs_list(self)
|
||||
fill_appvms_list(self)
|
||||
self.__init_restore_options__()
|
||||
|
||||
|
||||
def dev_combobox_activated(self, idx):
|
||||
dev_combobox_activated(self, idx)
|
||||
|
||||
|
||||
@pyqtSlot(name='on_select_path_button_clicked')
|
||||
def select_path_button_clicked(self):
|
||||
select_path_button_clicked(self)
|
||||
select_path_button_clicked(self, True)
|
||||
|
||||
def on_ignore_missing_toggled(self, checked):
|
||||
self.restore_options['use-default-template'] = checked
|
||||
@ -117,16 +123,37 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
|
||||
self.select_vms_widget.selected_list.clear()
|
||||
self.select_vms_widget.available_list.clear()
|
||||
|
||||
self.vms_to_restore = qubesutils.backup_restore_prepare(str(self.backup_dir), self.restore_options, self.qvm_collection)
|
||||
for vmname in self.vms_to_restore:
|
||||
self.select_vms_widget.available_list.addItem(vmname)
|
||||
|
||||
self.target_appvm = None
|
||||
if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
|
||||
self.target_appvm = self.qvm_collection.get_vm_by_name(
|
||||
str(self.appvm_combobox.currentText()))
|
||||
|
||||
try:
|
||||
self.restore_tmpdir, qubes_xml = backup.backup_restore_header(
|
||||
str(self.backup_location),
|
||||
str(self.passphrase_line_edit.text()),
|
||||
encrypted=self.encryption_checkbox.isChecked(),
|
||||
appvm=self.target_appvm)
|
||||
self.vms_to_restore = backup.backup_restore_prepare(
|
||||
str(self.backup_location),
|
||||
os.path.join(self.restore_tmpdir, qubes_xml),
|
||||
str(self.passphrase_line_edit.text()),
|
||||
options=self.restore_options,
|
||||
host_collection=self.qvm_collection,
|
||||
encrypt=self.encryption_checkbox.isChecked(),
|
||||
appvm=self.target_appvm)
|
||||
|
||||
for vmname in self.vms_to_restore:
|
||||
self.select_vms_widget.available_list.addItem(vmname)
|
||||
except QubesException as ex:
|
||||
QMessageBox.warning (None, "Restore error!", str(ex))
|
||||
|
||||
def __init_restore_options__(self):
|
||||
if not self.restore_options:
|
||||
self.restore_options = {}
|
||||
qubesutils.backup_restore_set_defaults(self.restore_options)
|
||||
|
||||
backup.backup_restore_set_defaults(self.restore_options)
|
||||
|
||||
if 'use-default-template' in self.restore_options and 'use-default-netvm' in self.restore_options:
|
||||
val = self.restore_options['use-default-template'] and self.restore_options['use-default-netvm']
|
||||
self.ignore_missing.setChecked(val)
|
||||
@ -139,25 +166,36 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
if 'dom0-home' in self.restore_options:
|
||||
self.skip_dom0.setChecked(self.restore_options['dom0-home'])
|
||||
|
||||
|
||||
|
||||
def gather_output(self, s):
|
||||
self.func_output.append(s)
|
||||
|
||||
def restore_error_output(self, s):
|
||||
self.emit(SIGNAL("restore_progress(QString)"), '<font color="red">{0}</font>'.format(s))
|
||||
|
||||
self.feedback_queue.put((SIGNAL("restore_progress(QString)"), '<font color="red">{0}</font>'.format(s)))
|
||||
|
||||
def restore_output(self, s):
|
||||
self.emit(SIGNAL("restore_progress(QString)"),'<font color="black">{0}</font>'.format(s))
|
||||
self.feedback_queue.put((SIGNAL("restore_progress(QString)"),'<font color="black">{0}</font>'.format(s)))
|
||||
|
||||
def update_progress_bar(self, value):
|
||||
print "progress %d" % value
|
||||
self.feedback_queue.put((SIGNAL("backup_progress(int)"), value))
|
||||
|
||||
def __do_restore__(self, thread_monitor):
|
||||
err_msg = []
|
||||
self.qvm_collection.lock_db_for_writing()
|
||||
try:
|
||||
qubesutils.backup_restore_do(str(self.backup_dir), self.vms_to_restore, self.qvm_collection, self.restore_output, self.restore_error_output)
|
||||
backup.backup_restore_do(
|
||||
str(self.backup_location),
|
||||
self.restore_tmpdir,
|
||||
str(self.passphrase_line_edit.text()),
|
||||
self.vms_to_restore,
|
||||
self.qvm_collection,
|
||||
encrypted=self.encryption_checkbox.isChecked(),
|
||||
appvm=self.target_appvm,
|
||||
print_callback=self.restore_output,
|
||||
error_callback=self.restore_error_output,
|
||||
progress_callback=self.update_progress_bar)
|
||||
except Exception as ex:
|
||||
print "Exception:",ex
|
||||
err_msg.append(str(ex))
|
||||
|
||||
self.qvm_collection.unlock_db()
|
||||
@ -169,7 +207,6 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
|
||||
thread_monitor.set_finished()
|
||||
|
||||
|
||||
def current_page_changed(self, id):
|
||||
|
||||
if self.currentPage() is self.select_vms_page:
|
||||
@ -185,16 +222,18 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
del self.vms_to_restore[str(vmname)]
|
||||
|
||||
del self.func_output[:]
|
||||
qubesutils.backup_restore_print_summary(self.vms_to_restore, print_callback = self.gather_output)
|
||||
backup.backup_restore_print_summary(
|
||||
self.vms_to_restore, print_callback = self.gather_output)
|
||||
self.confirm_text_edit.setReadOnly(True)
|
||||
self.confirm_text_edit.setFontFamily("Monospace")
|
||||
self.confirm_text_edit.setText("\n".join(self.func_output))
|
||||
|
||||
self.confirm_page.emit(SIGNAL("completeChanged()"))
|
||||
|
||||
elif self.currentPage() is self.commit_page:
|
||||
self.button(self.CancelButton).setDisabled(True)
|
||||
self.button(self.FinishButton).setDisabled(True)
|
||||
|
||||
|
||||
self.thread_monitor = ThreadMonitor()
|
||||
thread = threading.Thread (target= self.__do_restore__ , args=(self.thread_monitor,))
|
||||
thread.daemon = True
|
||||
@ -203,24 +242,35 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
|
||||
while not self.thread_monitor.is_finished():
|
||||
self.app.processEvents()
|
||||
time.sleep (0.1)
|
||||
try:
|
||||
for (signal,data) in iter(self.feedback_queue.get_nowait,None):
|
||||
self.emit(signal,data)
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
#if not self.thread_monitor.success:
|
||||
#QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg))
|
||||
|
||||
if self.dev_mount_path != None:
|
||||
umount_device(self.dev_mount_path)
|
||||
self.progress_bar.setValue(100)
|
||||
self.button(self.FinishButton).setEnabled(True)
|
||||
|
||||
def all_vms_good(self):
|
||||
for vminfo in self.vms_to_restore.values():
|
||||
if not vminfo['good-to-go']:
|
||||
print vminfo['vm'].name, str(vminfo)
|
||||
return False
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
if self.dev_mount_path != None:
|
||||
umount_device(self.dev_mount_path)
|
||||
self.done(0)
|
||||
|
||||
|
||||
|
||||
def has_selected_dir(self):
|
||||
return self.backup_dir != None
|
||||
|
||||
return self.backup_location != None
|
||||
|
||||
def has_selected_vms(self):
|
||||
return self.select_vms_widget.selected_list.count() > 0
|
||||
|
||||
|
232
restoredlg.ui
232
restoredlg.ui
@ -20,83 +20,21 @@
|
||||
<set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set>
|
||||
</property>
|
||||
<widget class="QWizardPage" name="select_dir_page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="3" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Backup source location</string>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>215</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Device</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="dev_combobox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>dev1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>longdeviceblablabla</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>dev2</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>dev3</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Backup directory:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="dir_line_edit"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QToolButton" name="select_path_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="options_groupbox">
|
||||
<property name="font">
|
||||
<font>
|
||||
@ -138,18 +76,127 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>215</height>
|
||||
</size>
|
||||
<property name="title">
|
||||
<string>Backup source location</string>
|
||||
</property>
|
||||
</spacer>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="dev_combobox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>dev1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>longdeviceblablabla</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>dev2</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>dev3</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="dir_line_edit"/>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<widget class="QToolButton" name="select_path_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Backup directory:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Device:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="appvm_combobox"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>AppVM:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Security options</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="encryption_checkbox">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Encrypted backup:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Decryption / Verification<br/>passphrase:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="passphrase_line_edit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -189,8 +236,8 @@
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html></string>
|
||||
</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;"><br /></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -217,6 +264,19 @@ p, li { white-space: pre-wrap; }
|
||||
<item>
|
||||
<widget class="QTextEdit" name="commit_text_edit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progress_bar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="format">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
Loading…
Reference in New Issue
Block a user