diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py
index d974842..fdc284c 100644
--- a/qubesmanager/backup.py
+++ b/qubesmanager/backup.py
@@ -23,9 +23,11 @@
import traceback
import signal
+import quamash
from qubesadmin import Qubes, exc
from qubesadmin import utils as admin_utils
+from qubesadmin import events
from qubes.storage.file import get_disk_usage
from PyQt4 import QtCore # pylint: disable=import-error
@@ -35,18 +37,38 @@ from . import multiselectwidget
from . import backup_utils
from . import utils
+
import grp
import pwd
import sys
import os
-from . import thread_monitor
-import threading
+import asyncio
+from contextlib import suppress
import time
+class BackupThread(QtCore.QThread):
+ def __init__(self, vm):
+ QtCore.QThread.__init__(self)
+ self.vm = vm
+ self.msg = None
+
+ def run(self):
+ msg = []
+ try:
+ if not self.vm.is_running():
+ self.vm.start()
+ self.vm.app.qubesd_call(
+ 'dom0', 'admin.backup.Execute',
+ backup_utils.get_profile_name(True))
+ except Exception as ex: # pylint: disable=broad-except
+ msg.append(str(ex))
+
+ if msg:
+ self.msg = '\n'.join(msg)
+
class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
-
- def __init__(self, qt_app, qubes_app, parent=None):
+ def __init__(self, qt_app, qubes_app, dispatcher, parent=None):
super(BackupVMsWindow, self).__init__(parent)
self.qt_app = qt_app
@@ -54,8 +76,6 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
self.backup_settings = QtCore.QSettings()
self.selected_vms = []
- self.canceled = False
- self.thread_monitor = None
self.setupUi(self)
@@ -112,6 +132,17 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
selected = self.load_settings()
self.__fill_vms_list__(selected)
+ # Connect backup events for progress_bar
+ self.progress_bar.setMinimum(0)
+ self.progress_bar.setMaximum(100)
+ self.dispatcher = dispatcher
+ dispatcher.add_handler('backup-progress', self.on_backup_progress)
+
+ def on_backup_progress(self, __submitter, _event, **kwargs):
+ print(kwargs['progress'])
+ self.progress_bar.setValue(int(float(kwargs['progress'])))
+
+
def load_settings(self):
"""
Helper function that tries to load existing backup profile
@@ -260,24 +291,6 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
return True
- def __do_backup__(self, t_monitor):
- msg = []
-
- try:
- vm = self.qubes_app.domains[
- self.appvm_combobox.currentText()]
- if not vm.is_running():
- vm.start()
- self.qubes_app.qubesd_call(
- 'dom0', 'admin.backup.Execute',
- backup_utils.get_profile_name(True))
- except Exception as ex: # pylint: disable=broad-except
- msg.append(str(ex))
-
- if msg:
- t_monitor.set_error_msg('\n'.join(msg))
-
- t_monitor.set_finished()
@staticmethod
def cleanup_temporary_files():
@@ -310,33 +323,27 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
self.showFileDialog.setChecked(self.showFileDialog.isEnabled()
and str(self.dir_line_edit.text())
.count("media/") > 0)
- self.thread_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(
- target=self.__do_backup__,
- args=(self.thread_monitor,))
- thread.daemon = True
- thread.start()
- while not self.thread_monitor.is_finished():
- self.qt_app.processEvents()
- time.sleep(0.1)
+ vm = self.qubes_app.domains[
+ self.appvm_combobox.currentText()]
+
+ self.thread = BackupThread(vm)
+ self.thread.finished.connect(self.backup_finished)
+ self.thread.start()
+
+ signal.signal(signal.SIGCHLD, old_sigchld_handler)
+
+ def backup_finished(self):
+ if self.thread.msg:
+ self.progress_status.setText(self.tr("Backup error."))
+ QtGui.QMessageBox.warning(
+ self, self.tr("Backup error!"),
+ self.tr("ERROR: {}").format(
+ self.thread.msg))
+ else:
+ self.progress_bar.setValue(100)
+ self.progress_status.setText(self.tr("Backup finished."))
- if not self.thread_monitor.success:
- if self.canceled:
- self.progress_status.setText(
- self.tr(
- "Backup aborted. "
- "Temporary file may be left at backup location."))
- else:
- self.progress_status.setText(self.tr("Backup error."))
- QtGui.QMessageBox.warning(
- self, self.tr("Backup error!"),
- self.tr("ERROR: {}").format(
- self.thread_monitor.error_msg))
- else:
- self.progress_bar.setMaximum(100)
- self.progress_bar.setValue(100)
- self.progress_status.setText(self.tr("Backup finished."))
if self.showFileDialog.isChecked():
orig_text = self.progress_status.text
self.progress_status.setText(
@@ -344,31 +351,28 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
" Please unmount your backup volume and cancel "
"the file selection dialog."))
backup_utils.select_path_button_clicked(self, False, True)
+
self.button(self.CancelButton).setEnabled(False)
self.button(self.FinishButton).setEnabled(True)
self.showFileDialog.setEnabled(False)
self.cleanup_temporary_files()
# turn off only when backup was successful
- if self.thread_monitor.success and \
- self.turn_off_checkbox.isChecked():
+ if self.turn_off_checkbox.isChecked():
os.system('systemctl poweroff')
- signal.signal(signal.SIGCHLD, old_sigchld_handler)
-
def reject(self):
if self.currentPage() is self.commit_page:
- self.canceled = True
+ self.thread.terminate()
self.qubes_app.qubesd_call(
'dom0', 'admin.backup.Cancel',
backup_utils.get_profile_name(True))
- self.progress_bar.setMaximum(100)
- self.progress_bar.setValue(0)
- self.button(self.CancelButton).setDisabled(True)
- self.cleanup_temporary_files()
- else:
- self.cleanup_temporary_files()
- self.done(0)
+ QtGui.QMessageBox.warning(
+ self, self.tr("Backup aborted!"),
+ self.tr("ERROR: {}").format("Aborted!"))
+
+ self.cleanup_temporary_files()
+ self.done(0)
def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0
@@ -402,9 +406,14 @@ def handle_exception(exc_type, exc_value, exc_traceback):
error + "at line %d of file %s.
"
% (line, filename))
+def loop_shutdown():
+ pending = asyncio.Task.all_tasks()
+ for task in pending:
+ with suppress(asyncio.CancelledError):
+ task.cancel()
+
def main():
-
qt_app = QtGui.QApplication(sys.argv)
qt_app.setOrganizationName("The Qubes Project")
qt_app.setOrganizationDomain("http://qubes-os.org")
@@ -412,14 +421,24 @@ def main():
sys.excepthook = handle_exception
- app = Qubes()
+ qubes_app = Qubes()
- backup_window = BackupVMsWindow(qt_app, app)
+ loop = quamash.QEventLoop(qt_app)
+ asyncio.set_event_loop(loop)
+ dispatcher = events.EventsDispatcher(qubes_app)
+ backup_window = BackupVMsWindow(qt_app, qubes_app, dispatcher)
backup_window.show()
- qt_app.exec_()
- qt_app.exit()
+ try:
+ loop.run_until_complete(
+ asyncio.ensure_future(dispatcher.listen_for_events()))
+ except asyncio.CancelledError:
+ pass
+ except Exception: # pylint: disable=broad-except
+ loop_shutdown()
+ exc_type, exc_value, exc_traceback = sys.exc_info()[:3]
+ handle_exception(exc_type, exc_value, exc_traceback)
if __name__ == "__main__":
diff --git a/qubesmanager/common_threads.py b/qubesmanager/common_threads.py
new file mode 100644
index 0000000..c7fd1ff
--- /dev/null
+++ b/qubesmanager/common_threads.py
@@ -0,0 +1,51 @@
+#!/usr/bin/python2
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2011 Marek Marczykowski
+#
+# 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 Lesser General Public License along
+# with this program; if not, see .
+#
+#
+
+
+from PyQt4 import QtCore # pylint: disable=import-error
+from qubesadmin import exc
+
+
+class RemoveVMThread(QtCore.QThread):
+ def __init__(self, vm):
+ QtCore.QThread.__init__(self)
+ self.vm = vm
+ self.error = None
+
+ def run(self):
+ try:
+ del self.vm.app.domains[self.vm.name]
+ except exc.QubesException as ex:
+ self.error("Error removing Qube!", str(ex))
+
+
+class CloneVMThread(QtCore.QThread):
+ def __init__(self, src_vm, dst_name):
+ QtCore.QThread.__init__(self)
+ self.src_vm = src_vm
+ self.dst_name = dst_name
+
+ def run(self):
+ try:
+ dst_vm = self.src_vm.app.clone_vm(self.src_vm, self.dst_name)
+ self.error = ("Sucess", "The qube was cloned sucessfully.")
+ except exc.QubesException as ex:
+ self.error = ("Error while cloning Qube!", str(ex))
diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py
index 6e7fa52..73959ad 100644
--- a/qubesmanager/create_new_vm.py
+++ b/qubesmanager/create_new_vm.py
@@ -35,7 +35,39 @@ import qubesadmin.exc
from . import utils
from .ui_newappvmdlg import Ui_NewVMDlg # pylint: disable=import-error
-from .thread_monitor import ThreadMonitor
+
+class CreateVMThread(QtCore.QThread):
+ def __init__(self, app, vmclass, name, label, template, properties):
+ QtCore.QThread.__init__(self)
+ self.app = app
+ self.vmclass = vmclass
+ self.name = name
+ self.label = label
+ self.template = template
+ self.properties = properties
+ self.error = None
+
+ def run(self):
+ try:
+ if self.vmclass == 'StandaloneVM' and self.template is not None:
+ if self.template is qubesadmin.DEFAULT:
+ src_vm = self.app.default_template
+ else:
+ src_vm = self.template
+ vm = self.app.clone_vm(src_vm, self.name, self.vmclass)
+ vm.label = self.label
+ for k, v in self.properties.items():
+ setattr(vm, k, v)
+ else:
+ vm = self.app.add_new_vm(self.vmclass,
+ name=self.name, label=self.label, template=self.template)
+ for k, v in self.properties.items():
+ setattr(vm, k, v)
+
+ except qubesadmin.exc.QubesException as qex:
+ self.error = str(qex)
+ except Exception as ex: # pylint: disable=broad-except
+ self.error = repr(ex)
class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
@@ -125,67 +157,36 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
properties['virt_mode'] = 'hvm'
properties['kernel'] = None
- thread_monitor = ThreadMonitor()
- thread = threading.Thread(target=self.do_create_vm,
- args=(self.app, vmclass, name, label, template, properties,
- thread_monitor))
- thread.daemon = True
- thread.start()
+ self.thread = CreateVMThread(self.app, vmclass, name, label,
+ template, properties)
+ self.thread.finished.connect(self.create_finished)
+ self.thread.start()
- progress = QtGui.QProgressDialog(
+ self.progress = QtGui.QProgressDialog(
self.tr("Creating new qube {}...").format(name), "", 0, 0)
- progress.setCancelButton(None)
- progress.setModal(True)
- progress.show()
+ self.progress.setCancelButton(None)
+ self.progress.setModal(True)
+ self.progress.show()
- while not thread_monitor.is_finished():
- self.qtapp.processEvents()
- time.sleep(0.1)
+ def create_finished(self):
+ self.progress.hide()
- progress.hide()
-
- if not thread_monitor.success:
+ if self.thread.error:
QtGui.QMessageBox.warning(None,
self.tr("Error creating the qube!"),
- self.tr("ERROR: {}").format(thread_monitor.error_msg))
+ self.tr("ERROR: {}").format(thread.error))
self.done(0)
- if thread_monitor.success:
+ if not self.thread.error:
if self.launch_settings.isChecked():
- subprocess.check_call(['qubes-vm-settings', name])
+ subprocess.check_call(['qubes-vm-settings', str(self.name)])
if self.install_system.isChecked():
subprocess.check_call(
- ['qubes-vm-boot-from-device', name])
+ ['qubes-vm-boot-from-device', srt(self.name)])
- @staticmethod
- def do_create_vm(app, vmclass, name, label, template, properties,
- thread_monitor):
- try:
- if vmclass == 'StandaloneVM' and template is not None:
- if template is qubesadmin.DEFAULT:
- src_vm = app.default_template
- else:
- src_vm = template
- vm = app.clone_vm(src_vm, name, vmclass)
- vm.label = label
- for k, v in properties.items():
- setattr(vm, k, v)
- else:
- vm = app.add_new_vm(vmclass,
- name=name, label=label, template=template)
- for k, v in properties.items():
- setattr(vm, k, v)
-
- except qubesadmin.exc.QubesException as qex:
- thread_monitor.set_error_msg(str(qex))
- except Exception as ex: # pylint: disable=broad-except
- thread_monitor.set_error_msg(repr(ex))
-
- thread_monitor.set_finished()
def type_change(self):
-
# AppVM
if self.vm_type.currentIndex() == 0:
self.template_vm.setEnabled(True)
diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py
index 63c8bf0..0022f59 100644
--- a/qubesmanager/qube_manager.py
+++ b/qubesmanager/qube_manager.py
@@ -25,12 +25,13 @@ import sys
import os
import os.path
import subprocess
-import time
from datetime import datetime, timedelta
import traceback
+from contextlib import suppress
+import time
+
import quamash
import asyncio
-from contextlib import suppress
from qubesadmin import Qubes
from qubesadmin import exc
@@ -51,6 +52,7 @@ from . import backup
from . import create_new_vm
from . import log_dialog
from . import utils as manager_utils
+from . import common_threads
class SearchBox(QtGui.QLineEdit):
@@ -241,32 +243,20 @@ class StartVMThread(QtCore.QThread):
def __init__(self, vm):
QtCore.QThread.__init__(self)
self.vm = vm
+ self.error = None
def run(self):
try:
self.vm.start()
except exc.QubesException as ex:
- self.emit(QtCore.SIGNAL('show_error(QString, QString)'),\
- "Error starting Qube!", str(ex))
+ self.error = ("Error starting Qube!", str(ex))
-class RemoveVMThread(QtCore.QThread):
- def __init__(self, vm, qubes_app):
- QtCore.QThread.__init__(self)
- self.vm = vm
- self.qubes_app = qubes_app
-
- def run(self):
- try:
- del self.qubes_app.domains[self.vm.name]
- except exc.QubesException as ex:
- self.emit(QtCore.SIGNAL('show_error(QString, QString)'),\
- "Error removing Qube!", str(ex))
-
class UpdateVMThread(QtCore.QThread):
def __init__(self, vm):
QtCore.QThread.__init__(self)
self.vm = vm
+ self.error = None
def run(self):
try:
@@ -279,23 +269,7 @@ class UpdateVMThread(QtCore.QThread):
self.vm.run_service("qubes.InstallUpdatesGUI",\
user="root", wait=False)
except (ChildProcessError, exc.QubesException) as ex:
- self.emit(QtCore.SIGNAL('show_error(QString, QString)'),\
- "Error on Qube update!", str(ex))
-
-
-class CloneVMThread(QtCore.QThread):
- def __init__(self, src_vm, qubes_app, dst_name):
- QtCore.QThread.__init__(self)
- self.src_vm = src_vm
- self.qubes_app = qubes_app
- self.dst_name = dst_name
-
- def run(self):
- try:
- dst_vm = self.qubes_app.clone_vm(self.src_vm, self.dst_name)
- except exc.QubesException as ex:
- self.emit(QtCore.SIGNAL('show_error(QString, QString)'),\
- "Error while cloning Qube!", str(ex))
+ self.error = ("Error on Qube update!", str(ex))
class RunCommandThread(QtCore.QThread):
@@ -308,8 +282,7 @@ class RunCommandThread(QtCore.QThread):
try:
self.vm.run(self.command_to_run)
except (ChildProcessError, exc.QubesException) as ex:
- self.emit(QtCore.SIGNAL('show_error(QString, QString)'),\
- "Error while running command!", str(ex))
+ self.error = ("Error while running command!", str(ex))
class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
@@ -330,7 +303,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
"IP": 8,
"Backups": 9,
"Last backup": 10,
- }
+ }
def __init__(self, qt_app, qubes_app, dispatcher, parent=None):
# pylint: disable=unused-argument
@@ -472,7 +445,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
dispatcher.add_handler('domain-start-failed',
self.on_domain_status_changed)
dispatcher.add_handler('domain-stopped', self.on_domain_status_changed)
+ dispatcher.add_handler('domain-pre-shutdown', self.on_domain_status_changed)
dispatcher.add_handler('domain-shutdown', self.on_domain_status_changed)
+ dispatcher.add_handler('domain-paused', self.on_domain_status_changed)
+ dispatcher.add_handler('domain-unpaused', self.on_domain_status_changed)
dispatcher.add_handler('domain-add', self.on_domain_added)
dispatcher.add_handler('domain-delete', self.on_domain_removed)
@@ -496,6 +472,12 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
def clear_threads(self):
for thread in self.threads_list:
if thread.isFinished():
+ if thread.error:
+ (title, msg) = thread.error
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr(title),
+ self.tr("ERROR: {0}").format(msg))
self.threads_list.remove(thread)
def closeEvent(self, event):
@@ -659,7 +641,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.manager_settings.sync()
def table_selection_changed(self):
-
vm = self.get_selected_vm()
if vm is not None and vm in self.qubes_app.domains:
@@ -783,7 +764,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
else:
# remove the VM
- thread = RemoveVMThread(vm, self.qubes_app)
+ thread = common_threads.RemoveVMThread(vm)
self.threads_list.append(thread)
self.connect(thread, QtCore.SIGNAL("show_error(QString, QString)"), self.show_error)
thread.finished.connect(self.clear_threads)
@@ -806,16 +787,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if not ok or clone_name == "":
return
- self.thread = CloneVMThread(vm, self.qubes_app, clone_name)
- self.thread.start()
- return
-
- progress = QtGui.QProgressDialog(
- self.tr("Cloning Qube {0} to {1}...").format(
- vm.name, clone_name), "", 0, 0)
- progress.setCancelButton(None)
- progress.setModal(True)
- progress.show()
+ thread = common_threads.CloneVMThread(vm, clone_name)
+ thread.finished.connect(self.clear_threads)
+ self.threads_list.append(thread)
+ thread.start()
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_resumevm_triggered')
@@ -825,8 +800,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if vm.get_power_state() in ["Paused", "Suspended"]:
try:
vm.unpause()
- self.vms_in_table[vm.qid].update()
- self.table_selection_changed()
except exc.QubesException as ex:
QtGui.QMessageBox.warning(
None, self.tr("Error unpausing Qube!"),
@@ -841,7 +814,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
thread = StartVMThread(vm)
self.threads_list.append(thread)
- self.connect(thread, QtCore.SIGNAL("show_error(QString, QString)"), self.show_error)
thread.finished.connect(self.clear_threads)
thread.start()
@@ -1016,7 +988,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
thread = UpdateVMThread(vm)
self.threads_list.append(thread)
- self.connect(thread, QtCore.SIGNAL("show_error(QString, QString)"), self.show_error)
thread.finished.connect(self.clear_threads)
thread.start()
@@ -1035,11 +1006,9 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
thread = RunCommandThread(vm, command_to_run)
self.threads_list.append(thread)
- self.connect(thread, QtCore.SIGNAL("show_error(QString, QString)"), self.show_error)
thread.finished.connect(self.clear_threads)
thread.start()
-
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_set_keyboard_layout_triggered')
def action_set_keyboard_layout_triggered(self):
@@ -1079,7 +1048,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_backup_triggered')
def action_backup_triggered(self):
- backup_window = backup.BackupVMsWindow(self.qt_app, self.qubes_app)
+ backup_window = backup.BackupVMsWindow(self.qt_app, self.qubes_app, self.dispatcher)
backup_window.show()
# noinspection PyArgumentList
diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py
index 18e628c..985e346 100644
--- a/qubesmanager/restore.py
+++ b/qubesmanager/restore.py
@@ -38,7 +38,6 @@ from qubes import backup
from . import ui_restoredlg # pylint: disable=no-name-in-module
from . import multiselectwidget
from . import backup_utils
-from . import thread_monitor
from multiprocessing import Queue, Event
from multiprocessing.queues import Empty
@@ -46,8 +45,37 @@ from qubesadmin import Qubes, exc
from qubesadmin.backup import restore
-class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
+class RestoreThread(QtCore.QThread):
+ def __init__(self, backup_restore, vms_to_restore):
+ QtCore.QThread.__init__(self)
+ self.backup_restore = backup_restore
+ self.vms_to_restore = vms_to_restore
+ self.error = None
+ self.msg = None
+ def run(self):
+ err_msg = []
+ try:
+ self.backup_restore.restore_do(self.vms_to_restore)
+
+ except backup.BackupCanceledError as ex:
+ self.canceled = True
+ err_msg.append(str(ex))
+ except Exception as ex: # pylint: disable=broad-except
+ err_msg.append(str(ex))
+ err_msg.append(
+ self.tr("Partially restored files left in /var/tmp/restore_*, "
+ "investigate them and/or clean them up"))
+ if err_msg:
+ self.error = '\n'.join(err_msg)
+ self.msg = '{0}'.format(
+ self.tr("Finished with errors!"))
+ else:
+ self.msg = '{0}'.format(
+ self.tr("Finished successfully!"))
+
+
+class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
def __init__(self, qt_app, qubes_app, parent=None):
super(RestoreVMsWindow, self).__init__(parent)
@@ -64,9 +92,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
logger.addHandler(handler)
logger.setLevel(logging.INFO)
- self.canceled = False
- self.error_detected = Event()
- self.thread_monitor = None
self.backup_restore = None
self.target_appvm = None
@@ -148,37 +173,8 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
def append_output(self, text):
self.commit_text_edit.append(text)
- def __do_restore__(self, t_monitor):
- err_msg = []
- try:
- self.backup_restore.restore_do(self.vms_to_restore)
-
- except backup.BackupCanceledError as ex:
- self.canceled = True
- err_msg.append(str(ex))
- except Exception as ex: # pylint: disable=broad-except
- err_msg.append(str(ex))
- err_msg.append(
- self.tr("Partially restored files left in /var/tmp/restore_*, "
- "investigate them and/or clean them up"))
-
- if self.canceled:
- self.append_output('{0}'.format(
- self.tr("Restore aborted!")))
- elif err_msg or self.error_detected.is_set():
- if err_msg:
- t_monitor.set_error_msg('\n'.join(err_msg))
- self.append_output('{0}'.format(
- self.tr("Finished with errors!")))
- else:
- self.append_output('{0}'.format(
- self.tr("Finished successfully!")))
-
- t_monitor.set_finished()
-
def current_page_changed(self, page_id): # pylint: disable=unused-argument
-
- old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+ self.old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
if self.currentPage() is self.select_vms_page:
self.__fill_vms_list__()
@@ -210,51 +206,57 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
and str(self.dir_line_edit.text())
.count("media/") > 0)
- self.thread_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=self.__do_restore__,
- args=(self.thread_monitor,))
- thread.daemon = True
- thread.start()
- while not self.thread_monitor.is_finished():
- self.qt_app.processEvents()
- time.sleep(0.1)
- try:
- log_record = self.feedback_queue.get_nowait()
- while log_record:
- if log_record.levelno == logging.ERROR or\
- log_record.levelno == logging.CRITICAL:
- output = '{0}'.format(
- log_record.getMessage())
- else:
- output = log_record.getMessage()
- self.append_output(output)
- log_record = self.feedback_queue.get_nowait()
- except Empty:
- pass
+ self.thread = RestoreThread(self.backup_restore, self.vms_to_restore)
+ self.thread.finished.connect(self.thread_finished)
- if not self.thread_monitor.success:
- if not self.canceled:
- QtGui.QMessageBox.warning(
- None,
- self.tr("Backup error!"),
- self.tr("ERROR: {0}").format(
- self.thread_monitor.error_msg))
- self.progress_bar.setMaximum(100)
- self.progress_bar.setValue(100)
+ # Start log timer
+ timer = QtCore.QTimer(self)
+ timer.timeout.connect(self.update_log)
+ timer.start(1000)
- if self.showFileDialog.isChecked():
- self.append_output(
- '{0}'.format(
- self.tr("Please unmount your backup volume and cancel "
- "the file selection dialog.")))
- self.qt_app.processEvents()
- backup_utils.select_path_button_clicked(self, False, True)
+ self.thread.start()
- self.button(self.FinishButton).setEnabled(True)
- self.button(self.CancelButton).setEnabled(False)
- self.showFileDialog.setEnabled(False)
+ def thread_finished(self):
+ self.progress_bar.setMaximum(100)
+ self.progress_bar.setValue(100)
+
+ if self.thread.error:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Backup error!"),
+ self.tr("ERROR: {0}").format(
+ self.thread.error))
+
+ if self.thread.msg:
+ self.append_output(self.thread.msg)
+
+ if self.showFileDialog.isChecked():
+ self.append_output(
+ '{0}'.format(
+ self.tr("Please unmount your backup volume and cancel "
+ "the file selection dialog.")))
+ backup_utils.select_path_button_clicked(self, False, True)
+
+ self.button(self.FinishButton).setEnabled(True)
+ self.button(self.CancelButton).setEnabled(False)
+ self.showFileDialog.setEnabled(False)
+ signal.signal(signal.SIGCHLD, self.old_sigchld_handler)
+
+ def update_log(self):
+ try:
+ log_record = self.feedback_queue.get_nowait()
+ while log_record:
+ if log_record.levelno == logging.ERROR or\
+ log_record.levelno == logging.CRITICAL:
+ output = '{0}'.format(
+ log_record.getMessage())
+ else:
+ output = log_record.getMessage()
+ self.append_output(output)
+ log_record = self.feedback_queue.get_nowait()
+ except Empty:
+ pass
- signal.signal(signal.SIGCHLD, old_sigchld_handler)
def all_vms_good(self):
for vm_info in self.vms_to_restore.values():
@@ -265,11 +267,13 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
return True
def reject(self):
- if self.currentPage() is self.commit_page:
+ if self.currentPage() is self.commit_page and self.thread.isRunning():
self.backup_restore.canceled = True
self.append_output('{0}'.format(
self.tr("Aborting the operation...")))
self.button(self.CancelButton).setDisabled(True)
+
+ self.thread.terminate()
else:
self.done(0)
diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py
index fcb749d..87845da 100755
--- a/qubesmanager/settings.py
+++ b/qubesmanager/settings.py
@@ -39,7 +39,7 @@ import qubesadmin.exc
from . import utils
from . import multiselectwidget
-from . import thread_monitor
+from . import common_threads
from . import device_list
from .appmenu_select import AppmenuSelectManager
@@ -49,6 +49,77 @@ from PyQt4 import QtCore, QtGui # pylint: disable=import-error
from . import ui_settingsdlg # pylint: disable=no-name-in-module
+class RenameVMThread(QtCore.QThread):
+ def __init__(self, vm, new_vm_name, dependencies):
+ QtCore.QThread.__init__(self)
+ self.vm = vm
+ self.new_vm_name = new_vm_name
+ self.dependencies = dependencies
+
+ def run(self):
+ try:
+ new_vm = self.vm.app.clone_vm(self.vm, self.new_vm_name)
+
+ failed_props = []
+
+ for (holder, prop) in self.dependencies:
+ try:
+ if holder is None:
+ setattr(self.vm.app, prop, new_vm)
+ else:
+ setattr(holder, prop, new_vm)
+ except exc.QubesException as qex:
+ failed_props += [(holder, prop)]
+
+ if not failed_props:
+ del self.vm.app.domains[self.vm.name]
+ else:
+ list_text = utils.format_dependencies_list(failed_props)
+
+ QtGui.QMessageBox.warning(
+ self,
+ self.tr("Warning: rename partially unsuccessful"),
+ self.tr("Some properties could not be changed to the new "
+ "name. The system has now both {} and {} qubes. "
+ "To resolve this, please check and change the "
+ "following properties and remove the qube {} "
+ "manually.
").format(
+ self.vm.name, name, self.vm.name) + list_text)
+
+ except exc.QubesException as qex:
+ self.error = ("Rename error!", str(ex))
+ except Exception as ex: # pylint: disable=broad-except
+ self.error = ("Rename error!", repr(ex))
+
+
+class RefreshAppsVMThread(QtCore.QThread):
+ def __init__(self, vm):
+ QtCore.QThread.__init__(self)
+ self.error = None
+ self.vm = vm
+
+ def run(self):
+ try:
+ try:
+ target_vm = self.vm.template
+ except AttributeError:
+ target_vm = self.vm
+
+ if not target_vm.is_running():
+ not_running = True
+ target_vm.start()
+ else:
+ not_running = False
+
+ subprocess.check_call(['qvm-sync-appmenus', target_vm.name])
+
+ if not_running:
+ target_vm.shutdown()
+
+ except Exception as ex:
+ self.error = ("Refresh failed!", str(ex))
+
+
# pylint: disable=too-many-instance-attributes
class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
tabs_indices = collections.OrderedDict((
@@ -65,6 +136,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.vm = vm
self.qapp = qapp
+ self.threads_list = []
try:
self.source_vm = self.vm.template
except AttributeError:
@@ -150,6 +222,18 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.refresh_apps_button.clicked.connect(
self.refresh_apps_button_pressed)
+ def clear_threads(self):
+ for thread in self.threads_list:
+ if thread.isFinished():
+ if thread.error:
+ (title, msg) = thread.error
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr(title),
+ self.tr("ERROR: {0}").format(msg))
+
+ self.threads_list.remove(thread)
+
def keyPressEvent(self, event): # pylint: disable=invalid-name
if event.key() == QtCore.Qt.Key_Enter \
or event.key() == QtCore.Qt.Key_Return:
@@ -164,31 +248,14 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
pass
def save_changes(self):
- t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=self.__save_changes__,
- args=(t_monitor,))
- thread.daemon = True
- thread.start()
+ error = self.__save_changes__()
- progress = QtGui.QProgressDialog(
- self.tr("Applying settings to {0}...").format(self.vm.name),
- "", 0, 0)
- progress.setCancelButton(None)
- progress.setModal(True)
- progress.show()
-
- while not t_monitor.is_finished():
- self.qapp.processEvents()
- time.sleep(0.1)
-
- progress.hide()
-
- if not t_monitor.success:
+ if error:
QtGui.QMessageBox.warning(
self,
self.tr("Error while changing settings for {0}!"
).format(self.vm.name),
- self.tr("ERROR: {0}").format(t_monitor.error_msg))
+ self.tr("ERROR: {0}").format('\n'.join(ret)))
def apply(self):
self.save_changes()
@@ -197,9 +264,9 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.save_changes()
self.done(0)
- def __save_changes__(self, t_monitor):
-
+ def __save_changes__(self):
ret = []
+
try:
ret_tmp = self.__apply_basic_tab__()
if ret_tmp:
@@ -237,12 +304,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
except Exception as ex: # pylint: disable=broad-except
ret += [self.tr("Applications tab:"), repr(ex)]
- if ret:
- t_monitor.set_error_msg('\n'.join(ret))
-
utils.debug('\n'.join(ret))
-
- t_monitor.set_finished()
+ return ret
def check_network_availability(self):
netvm = self.vm.netvm
@@ -464,61 +527,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
"allowed value."))
self.init_mem.setValue(self.max_mem_size.value() / 10)
- def _run_in_thread(self, func, *args):
- t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=func, args=(t_monitor, *args,))
- thread.daemon = True
- thread.start()
-
- while not t_monitor.is_finished():
- self.qapp.processEvents()
- time.sleep(0.1)
-
- if not t_monitor.success:
- QtGui.QMessageBox.warning(self,
- self.tr("Error!"),
- self.tr("ERROR: {}").format(
- t_monitor.error_msg))
- return False
- return True
-
- def _rename_vm(self, t_monitor, name, dependencies):
- try:
- new_vm = self.vm.app.clone_vm(self.vm, name)
-
- failed_props = []
-
- for (holder, prop) in dependencies:
- try:
- if holder is None:
- setattr(self.vm.app, prop, new_vm)
- else:
- setattr(holder, prop, new_vm)
- except qubesadmin.exc.QubesException as qex:
- failed_props += [(holder, prop)]
-
- if not failed_props:
- del self.vm.app.domains[self.vm.name]
- else:
- list_text = utils.format_dependencies_list(failed_props)
-
- QtGui.QMessageBox.warning(
- self,
- self.tr("Warning: rename partially unsuccessful"),
- self.tr("Some properties could not be changed to the new "
- "name. The system has now both {} and {} qubes. "
- "To resolve this, please check and change the "
- "following properties and remove the qube {} "
- "manually.
").format(
- self.vm.name, name, self.vm.name) + list_text)
-
- except qubesadmin.exc.QubesException as qex:
- t_monitor.set_error_msg(str(qex))
- except Exception as ex: # pylint: disable=broad-except
- t_monitor.set_error_msg(repr(ex))
-
- t_monitor.set_finished()
-
def rename_vm(self):
dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm)
@@ -544,19 +552,12 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr('New name: (WARNING: all other changes will be discarded)'))
if ok:
- if self._run_in_thread(self._rename_vm, new_vm_name, dependencies):
- self.done(0)
-
- def _remove_vm(self, t_monitor):
- try:
- del self.vm.app.domains[self.vm.name]
-
- except qubesadmin.exc.QubesException as qex:
- t_monitor.set_error_msg(str(qex))
- except Exception as ex: # pylint: disable=broad-except
- t_monitor.set_error_msg(repr(ex))
-
- t_monitor.set_finished()
+ thread = RenameVMThread(self.vm, new_vm_name, dependencies)
+ self.threads_list.append(thread)
+ thread.finished.connect(self.clear_threads)
+ thread.start()
+ thread.wait()
+ #self.done(0)
def remove_vm(self):
@@ -584,7 +585,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
'qube\'s name below.'))
if ok and answer == self.vm.name:
- self._run_in_thread(self._remove_vm)
+ thread = common_threads.RemoveVMThread(self.vm)
+ thread.start()
self.done(0)
elif ok:
@@ -612,11 +614,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr('Name for the cloned qube:'))
if ok:
- self._run_in_thread(self._clone_vm, cloned_vm_name)
- QtGui.QMessageBox.warning(
- self,
- self.tr("Success"),
- self.tr("The qube was cloned successfully."))
+ thread = common_threads.CloneVMThread(self.vm, cloned_vm_name)
+ thread.finished.connect(self.clear_threads)
+ self.threads_list.append(thread)
+ thread.start()
######### advanced tab
@@ -944,43 +945,19 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
######## applications tab
- def refresh_apps_in_vm(self, t_monitor):
- try:
- target_vm = self.vm.template
- except AttributeError:
- target_vm = self.vm
-
- if not target_vm.is_running():
- not_running = True
- target_vm.start()
- else:
- not_running = False
-
- subprocess.check_call(['qvm-sync-appmenus', target_vm.name])
-
- if not_running:
- target_vm.shutdown()
-
- t_monitor.set_finished()
-
def refresh_apps_button_pressed(self):
self.refresh_apps_button.setEnabled(False)
self.refresh_apps_button.setText(self.tr('Refresh in progress...'))
- t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(
- target=self.refresh_apps_in_vm,
- args=(t_monitor,))
- thread.daemon = True
+ thread = RefreshAppsVMThread(self.vm)
+ thread.finished.connect(self.clear_threads)
+ thread.finished.connect(self.refresh_finished)
+ self.threads_list.append(thread)
thread.start()
- while not t_monitor.is_finished():
- self.qapp.processEvents()
- time.sleep(0.1)
-
+ def refresh_finished(self):
self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list)
-
self.refresh_apps_button.setEnabled(True)
self.refresh_apps_button.setText(self.tr('Refresh Applications'))
diff --git a/qubesmanager/thread_monitor.py b/qubesmanager/thread_monitor.py
deleted file mode 100644
index 8515aff..0000000
--- a/qubesmanager/thread_monitor.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/python2
-#
-# The Qubes OS Project, http://www.qubes-os.org
-#
-# Copyright (C) 2011 Marek Marczykowski
-#
-# 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 Lesser General Public License along
-# with this program; if not, see .
-#
-#
-
-
-import PyQt4.QtCore # pylint: disable=import-error
-
-import threading
-
-class ThreadMonitor(PyQt4.QtCore.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()