Merge branch 'new-backups'

This commit is contained in:
Marek Marczykowski-Górecki 2013-11-29 04:01:55 +01:00
commit fda7180d16
5 changed files with 448 additions and 211 deletions

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>700</width> <width>650</width>
<height>399</height> <height>399</height>
</rect> </rect>
</property> </property>
@ -148,21 +148,21 @@
</layout> </layout>
</widget> </widget>
<widget class="QWizardPage" name="select_dir_page"> <widget class="QWizardPage" name="select_dir_page">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QGridLayout" name="gridLayout_5">
<item> <item row="0" column="0">
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <property name="title">
<string>Backup destination directory</string> <string>Backup destination directory</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Device:</string> <string>Device:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="1" column="2">
<widget class="QComboBox" name="dev_combobox"> <widget class="QComboBox" name="dev_combobox">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
@ -192,23 +192,93 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="4" column="2">
<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"/> <widget class="QLineEdit" name="dir_line_edit"/>
</item> </item>
<item row="1" column="2"> <item row="4" column="3">
<widget class="QToolButton" name="select_path_button"> <widget class="QToolButton" name="select_path_button">
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Encryption / Verification&lt;br/&gt;passphrase:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reenter passphrase:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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> </layout>
</widget> </widget>
</item> </item>
@ -238,8 +308,8 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -264,7 +334,7 @@ p, li { white-space: pre-wrap; }
<widget class="QWizardPage" name="commit_page"> <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="progress_status">
<property name="font"> <property name="font">
<font> <font>
<pointsize>9</pointsize> <pointsize>9</pointsize>

View File

@ -31,6 +31,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 backup
from qubes import qubesutils from qubes import qubesutils
import qubesmanager.resources_rc import qubesmanager.resources_rc
@ -65,21 +66,22 @@ class BackupVMsWindow(Ui_Backup, QWizard):
self.shutdown_vm_func = shutdown_vm_func self.shutdown_vm_func = shutdown_vm_func
self.dev_mount_path = None self.dev_mount_path = None
self.backup_dir = None self.backup_location = None
self.func_output = [] self.func_output = []
self.excluded = [] self.selected_vms = []
for vm in self.qvm_collection.values(): for vm in self.qvm_collection.values():
if vm.qid == 0: if vm.qid == 0:
self.vm = vm self.vm = vm
break; break;
assert self.vm != None assert self.vm != None
self.setupUi(self) self.setupUi(self)
self.progress_status.text = "Backup in progress..."
self.show_running_vms_warning(False) 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.select_vms_widget = MultiSelectWidget(self)
self.verticalLayout.insertWidget(1, self.select_vms_widget) 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.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.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated)
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue) 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_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 #FIXME
#this causes to run isComplete() twice, I don't know why #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.total_size = 0
self.__fill_vms_list__() self.__fill_vms_list__()
fill_devs_list(self) fill_devs_list(self)
fill_appvms_list(self)
def show_running_vms_warning(self, show): def show_running_vms_warning(self, show):
self.running_vms_warning.setVisible(show) self.running_vms_warning.setVisible(show)
self.shutdown_running_vms_button.setVisible(show) self.shutdown_running_vms_button.setVisible(show)
@ -114,11 +130,11 @@ class BackupVMsWindow(Ui_Backup, QWizard):
if vm.qid == 0: if vm.qid == 0:
local_user = grp.getgrnam('qubes').gr_mem[0] local_user = grp.getgrnam('qubes').gr_mem[0]
home_dir = pwd.getpwnam(local_user).pw_dir 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: 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) + ")") super(BackupVMsWindow.VmListItem, self).__init__(vm.name+ " (" + qubesutils.size_to_human(self.size) + ")")
def get_vm_size(self, vm): def get_vm_size(self, vm):
size = 0 size = 0
if vm.private_img is not None: if vm.private_img is not None:
@ -167,7 +183,7 @@ class BackupVMsWindow(Ui_Backup, QWizard):
item.setForeground(QBrush(QColor(0, 0, 0))) item.setForeground(QBrush(QColor(0, 0, 0)))
self.show_running_vms_warning(some_selected_vms_running) self.show_running_vms_warning(some_selected_vms_running)
for i in range(self.select_vms_widget.available_list.count()): for i in range(self.select_vms_widget.available_list.count()):
item = self.select_vms_widget.available_list.item(i) item = self.select_vms_widget.available_list.item(i)
if item.vm.is_running() and item.vm.qid != 0: if item.vm.is_running() and item.vm.qid != 0:
@ -193,7 +209,7 @@ class BackupVMsWindow(Ui_Backup, QWizard):
self.app.processEvents() self.app.processEvents()
if reply == QMessageBox.Yes: if reply == QMessageBox.Yes:
wait_time = 60.0 wait_time = 60.0
for vm in vms: for vm in vms:
self.shutdown_vm_func(vm, wait_time*1000) self.shutdown_vm_func(vm, wait_time*1000)
@ -210,7 +226,6 @@ class BackupVMsWindow(Ui_Backup, QWizard):
progress.hide() progress.hide()
def get_running_vms(self): def get_running_vms(self):
names = [] names = []
vms = [] vms = []
@ -221,12 +236,8 @@ class BackupVMsWindow(Ui_Backup, QWizard):
vms.append(item.vm) vms.append(item.vm)
return (names, vms) return (names, vms)
def dev_combobox_activated(self, idx): def dev_combobox_activated(self, idx):
dev_combobox_activated(self, idx) dev_combobox_activated(self, idx)
@pyqtSlot(name='on_select_path_button_clicked') @pyqtSlot(name='on_select_path_button_clicked')
def select_path_button_clicked(self): def select_path_button_clicked(self):
@ -234,37 +245,51 @@ class BackupVMsWindow(Ui_Backup, QWizard):
def validateCurrentPage(self): def validateCurrentPage(self):
if self.currentPage() is self.select_vms_page: if self.currentPage() is self.select_vms_page:
for i in range(self.select_vms_widget.selected_list.count()): if self.check_running():
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.")
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
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 return True
def gather_output(self, s): def gather_output(self, s):
self.func_output.append(s) self.func_output.append(s)
def update_progress_bar(self, value): def update_progress_bar(self, value):
if value == 100: self.emit(SIGNAL("backup_progress(int)"), value)
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
def __do_backup__(self, thread_monitor): def __do_backup__(self, thread_monitor):
msg = [] msg = []
try: try:
qubesutils.backup_do(str(self.backup_dir), self.files_to_backup, self.update_progress_bar) backup.backup_do(str(self.backup_location),
#simulate_long_lasting_proces(10, self.update_progress_bar) 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: except Exception as ex:
print "Exception:",ex
msg.append(str(ex)) msg.append(str(ex))
if len(msg) > 0 : if len(msg) > 0 :
@ -272,14 +297,24 @@ class BackupVMsWindow(Ui_Backup, QWizard):
thread_monitor.set_finished() thread_monitor.set_finished()
def current_page_changed(self, id): def current_page_changed(self, id):
if self.currentPage() is self.confirm_page: 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[:] del self.func_output[:]
try: 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: 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.setReadOnly(True)
self.textEdit.setFontFamily("Monospace") self.textEdit.setFontFamily("Monospace")
@ -289,7 +324,6 @@ class BackupVMsWindow(Ui_Backup, QWizard):
self.button(self.FinishButton).setDisabled(True) self.button(self.FinishButton).setDisabled(True)
self.button(self.CancelButton).setDisabled(True) self.button(self.CancelButton).setDisabled(True)
self.thread_monitor = ThreadMonitor() 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 = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,))
thread.daemon = True thread.daemon = True
thread.start() thread.start()
@ -299,52 +333,52 @@ class BackupVMsWindow(Ui_Backup, QWizard):
while not self.thread_monitor.is_finished(): while not self.thread_monitor.is_finished():
self.app.processEvents() self.app.processEvents()
time.sleep (0.1) 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: 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: if self.dev_mount_path != None:
umount_device(self.dev_mount_path) umount_device(self.dev_mount_path)
self.button(self.FinishButton).setEnabled(True) self.button(self.FinishButton).setEnabled(True)
def reject(self): def reject(self):
#cancell clicked while the backup is in progress. #cancell clicked while the backup is in progress.
#calling kill on cp. #calling kill on tar.
if self.currentPage() is self.commit_page: if self.currentPage() is self.commit_page:
manager_pid = os.getpid() manager_pid = os.getpid()
cp_pid_cmd = ["ps" ,"--ppid", str(manager_pid)] archive_pid_cmd = ["ps" ,"--ppid", str(manager_pid)]
pid = None
while not self.thread_monitor.is_finished(): while not self.thread_monitor.is_finished():
cp_pid = subprocess.Popen(cp_pid_cmd, stdout = subprocess.PIPE) archive_pid = subprocess.Popen(archive_pid_cmd, stdout = subprocess.PIPE)
output = cp_pid.stdout.read().split("\n") output = archive_pid.stdout.readlines()
for l in output: for l in output:
if l.endswith("cp"): if l.strip().endswith("tar"):
pid = l.split(" ")[1] os.kill(int(l.split(" ")[0]), signal.SIGTERM)
break time.sleep(0.1)
if pid != None:
os.kill(int(pid), signal.SIGTERM)
break
if self.dev_mount_path != None: if self.dev_mount_path != None:
umount_device(self.dev_mount_path) umount_device(self.dev_mount_path)
self.done(0) self.done(0)
def has_selected_vms(self): def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0 return self.select_vms_widget.selected_list.count() > 0
def has_selected_dir(self): def has_selected_dir_and_pass(self):
return self.backup_dir != None 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: # Bases on the original code by:

View File

@ -75,11 +75,25 @@ def umount_device(dev_mount_path):
if button == QMessageBox.Ok: if button == QMessageBox.Ok:
return dev_mount_path 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): def fill_devs_list(dialog):
dialog.dev_combobox.clear() dialog.dev_combobox.clear()
dialog.dev_combobox.addItem("None") dialog.dev_combobox.addItem("None")
dialog.blk_manager.blk_lock.acquire() dialog.blk_manager.blk_lock.acquire()
for a in dialog.blk_manager.attached_devs: for a in dialog.blk_manager.attached_devs:
if dialog.blk_manager.attached_devs[a]['attached_to']['vm'] == dialog.vm.name : 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): def enable_dir_line_edit(dialog, boolean):
dialog.dir_line_edit.setEnabled(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): def dev_combobox_activated(dialog, idx):
@ -107,7 +121,7 @@ def dev_combobox_activated(dialog, idx):
#there was a change #there was a change
dialog.dir_line_edit.setText("") dialog.dir_line_edit.setText("")
dialog.backup_dir = None dialog.backup_location = None
if dialog.dev_mount_path != None: if dialog.dev_mount_path != None:
dialog.dev_mount_path = umount_device(dialog.dev_mount_path) 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) dialog.dev_combobox.setCurrentIndex(dialog.prev_dev_idx)
return 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()) dev_name = str(dialog.dev_combobox.itemData(idx).toString())
dialog.blk_manager.blk_lock.acquire() 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.dev_combobox.setCurrentIndex(0) #if couldn't mount - set current device to "None"
dialog.prev_dev_idx = 0 dialog.prev_dev_idx = 0
return return
dialog.prev_dev_idx = idx dialog.prev_dev_idx = idx
if dialog.dev_mount_path != None: if dialog.dev_mount_path != None:
# Initialize path with root of mounted device # Initialize path with root of mounted device
dialog.dir_line_edit.setText(dialog.dev_mount_path) 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()")) dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
def select_path_button_clicked(dialog): def select_path_button_clicked(dialog, select_file = False):
dialog.backup_dir = dialog.dir_line_edit.text() dialog.backup_location = dialog.dir_line_edit.text()
file_dialog = QFileDialog() file_dialog = QFileDialog()
file_dialog.setReadOnly(True) file_dialog.setReadOnly(True)
if dialog.dev_mount_path != None: if select_file:
new_path = file_dialog.getExistingDirectory(dialog, "Select backup directory.", dialog.dev_mount_path) file_dialog_function = file_dialog.getOpenFileName
else: else:
new_path = file_dialog.getExistingDirectory(dialog, "Select backup directory.", "~") file_dialog_function = file_dialog.getExistingDirectory
if new_path: 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.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()")) dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
def simulate_long_lasting_proces(period, progress_callback): def simulate_long_lasting_proces(period, progress_callback):
for i in range(period): for i in range(period):
progress_callback((i*100)/period) progress_callback((i*100)/period)

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.qubes import qubes_base_dir
import qubesmanager.resources_rc import qubesmanager.resources_rc
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
@ -39,18 +40,19 @@ import time
from operator import itemgetter from operator import itemgetter
from thread_monitor import * from thread_monitor import *
from qubes import backup
from qubes import qubesutils from qubes import qubesutils
from ui_restoredlg import * from ui_restoredlg import *
from multiselectwidget import * from multiselectwidget import *
from backup_utils import * from backup_utils import *
from multiprocessing import Queue
from multiprocessing.queues import Empty
class RestoreVMsWindow(Ui_Restore, QWizard): 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): def __init__(self, app, qvm_collection, blk_manager, parent=None):
super(RestoreVMsWindow, self).__init__(parent) super(RestoreVMsWindow, self).__init__(parent)
@ -60,10 +62,11 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
self.blk_manager = blk_manager self.blk_manager = blk_manager
self.dev_mount_path = None self.dev_mount_path = None
self.backup_dir = None self.backup_location = None
self.restore_options = None self.restore_options = None
self.backup_vms_list = None self.backup_vms_list = None
self.func_output = [] self.func_output = []
self.feedback_queue = Queue()
self.excluded = {} self.excluded = {}
@ -71,7 +74,7 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
if vm.qid == 0: if vm.qid == 0:
self.vm = vm self.vm = vm
break; break;
assert self.vm != None assert self.vm != None
self.setupUi(self) 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, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
self.connect(self.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated) 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("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_dir_page.isComplete = self.has_selected_dir
self.select_vms_page.isComplete = self.has_selected_vms self.select_vms_page.isComplete = self.has_selected_vms
self.confirm_page.isComplete = self.all_vms_good
#FIXME #FIXME
#this causes to run isComplete() twice, I don't know why #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_devs_list(self)
fill_appvms_list(self)
self.__init_restore_options__() self.__init_restore_options__()
def dev_combobox_activated(self, idx): def dev_combobox_activated(self, idx):
dev_combobox_activated(self, idx) dev_combobox_activated(self, idx)
@pyqtSlot(name='on_select_path_button_clicked') @pyqtSlot(name='on_select_path_button_clicked')
def select_path_button_clicked(self): def select_path_button_clicked(self):
select_path_button_clicked(self) select_path_button_clicked(self, True)
def on_ignore_missing_toggled(self, checked): def on_ignore_missing_toggled(self, checked):
self.restore_options['use-default-template'] = 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.selected_list.clear()
self.select_vms_widget.available_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) self.target_appvm = None
for vmname in self.vms_to_restore: if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
self.select_vms_widget.available_list.addItem(vmname) 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): def __init_restore_options__(self):
if not self.restore_options: if not self.restore_options:
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: 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'] val = self.restore_options['use-default-template'] and self.restore_options['use-default-netvm']
self.ignore_missing.setChecked(val) self.ignore_missing.setChecked(val)
@ -139,25 +166,36 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
if 'dom0-home' in self.restore_options: if 'dom0-home' in self.restore_options:
self.skip_dom0.setChecked(self.restore_options['dom0-home']) self.skip_dom0.setChecked(self.restore_options['dom0-home'])
def gather_output(self, s): def gather_output(self, s):
self.func_output.append(s) self.func_output.append(s)
def restore_error_output(self, 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): 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): def __do_restore__(self, thread_monitor):
err_msg = [] err_msg = []
self.qvm_collection.lock_db_for_writing() self.qvm_collection.lock_db_for_writing()
try: 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: except Exception as ex:
print "Exception:",ex
err_msg.append(str(ex)) err_msg.append(str(ex))
self.qvm_collection.unlock_db() self.qvm_collection.unlock_db()
@ -169,7 +207,6 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
thread_monitor.set_finished() thread_monitor.set_finished()
def current_page_changed(self, id): def current_page_changed(self, id):
if self.currentPage() is self.select_vms_page: 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.vms_to_restore[str(vmname)]
del self.func_output[:] 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.setReadOnly(True)
self.confirm_text_edit.setFontFamily("Monospace") self.confirm_text_edit.setFontFamily("Monospace")
self.confirm_text_edit.setText("\n".join(self.func_output)) self.confirm_text_edit.setText("\n".join(self.func_output))
self.confirm_page.emit(SIGNAL("completeChanged()"))
elif self.currentPage() is self.commit_page: elif self.currentPage() is self.commit_page:
self.button(self.CancelButton).setDisabled(True) self.button(self.CancelButton).setDisabled(True)
self.button(self.FinishButton).setDisabled(True) self.button(self.FinishButton).setDisabled(True)
self.thread_monitor = ThreadMonitor() self.thread_monitor = ThreadMonitor()
thread = threading.Thread (target= self.__do_restore__ , args=(self.thread_monitor,)) thread = threading.Thread (target= self.__do_restore__ , args=(self.thread_monitor,))
thread.daemon = True thread.daemon = True
@ -203,24 +242,35 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
while not self.thread_monitor.is_finished(): while not self.thread_monitor.is_finished():
self.app.processEvents() self.app.processEvents()
time.sleep (0.1) 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: #if not self.thread_monitor.success:
#QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg)) #QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg))
if self.dev_mount_path != None: if self.dev_mount_path != None:
umount_device(self.dev_mount_path) umount_device(self.dev_mount_path)
self.progress_bar.setValue(100)
self.button(self.FinishButton).setEnabled(True) 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): def reject(self):
if self.dev_mount_path != None: if self.dev_mount_path != None:
umount_device(self.dev_mount_path) umount_device(self.dev_mount_path)
self.done(0) self.done(0)
def has_selected_dir(self): def has_selected_dir(self):
return self.backup_dir != None return self.backup_location != None
def has_selected_vms(self): def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0 return self.select_vms_widget.selected_list.count() > 0

View File

@ -20,83 +20,21 @@
<set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set> <set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set>
</property> </property>
<widget class="QWizardPage" name="select_dir_page"> <widget class="QWizardPage" name="select_dir_page">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QGridLayout" name="gridLayout_3">
<item> <item row="3" column="0">
<widget class="QGroupBox" name="groupBox_2"> <spacer name="verticalSpacer">
<property name="font"> <property name="orientation">
<font> <enum>Qt::Vertical</enum>
<weight>50</weight>
<bold>false</bold>
</font>
</property> </property>
<property name="title"> <property name="sizeHint" stdset="0">
<string>Backup source location</string> <size>
<width>20</width>
<height>215</height>
</size>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> </spacer>
<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>
</item> </item>
<item> <item row="1" column="0">
<widget class="QGroupBox" name="options_groupbox"> <widget class="QGroupBox" name="options_groupbox">
<property name="font"> <property name="font">
<font> <font>
@ -138,18 +76,127 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item row="0" column="0">
<spacer name="verticalSpacer"> <widget class="QGroupBox" name="groupBox_2">
<property name="orientation"> <property name="font">
<enum>Qt::Vertical</enum> <font>
<weight>50</weight>
<bold>false</bold>
</font>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="title">
<size> <string>Backup source location</string>
<width>20</width>
<height>215</height>
</size>
</property> </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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Decryption / Verification&lt;br/&gt;passphrase:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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> </item>
</layout> </layout>
</widget> </widget>
@ -189,8 +236,8 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -217,6 +264,19 @@ p, li { white-space: pre-wrap; }
<item> <item>
<widget class="QTextEdit" name="commit_text_edit"/> <widget class="QTextEdit" name="commit_text_edit"/>
</item> </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> </layout>
</widget> </widget>
</widget> </widget>