diff --git a/qubesmanager.pro b/qubesmanager.pro
index 51f4d1c..f186c52 100644
--- a/qubesmanager.pro
+++ b/qubesmanager.pro
@@ -17,6 +17,7 @@ SOURCES = \
qubesmanager/block.py \
qubesmanager/clipboard.py \
qubesmanager/create_new_vm.py \
+ qubesmanager/common_threads.py \
qubesmanager/firewall.py \
qubesmanager/global_settings.py \
qubesmanager/log_dialog.py \
@@ -27,7 +28,6 @@ SOURCES = \
qubesmanager/restore.py \
qubesmanager/settings.py \
qubesmanager/table_widgets.py \
- qubesmanager/thread_monitor.py \
qubesmanager/ui_about.py \
qubesmanager/ui_backupdlg.py \
qubesmanager/ui_globalsettingsdlg.py \
diff --git a/qubesmanager/about.py b/qubesmanager/about.py
index b12ae36..da54508 100644
--- a/qubesmanager/about.py
+++ b/qubesmanager/about.py
@@ -20,7 +20,6 @@
# with this program; if not, see .
#
#
-from PyQt4.QtCore import SIGNAL, SLOT # pylint: disable=import-error
from PyQt4.QtGui import QDialog, QIcon # pylint: disable=import-error
from qubesmanager.releasenotes import ReleaseNotesDialog
from qubesmanager.informationnotes import InformationNotesDialog
@@ -28,6 +27,7 @@ from qubesmanager.informationnotes import InformationNotesDialog
from . import ui_about # pylint: disable=no-name-in-module
+# pylint: disable=too-few-public-methods
class AboutDialog(ui_about.Ui_AboutDialog, QDialog):
def __init__(self):
super(AboutDialog, self).__init__()
@@ -38,18 +38,14 @@ class AboutDialog(ui_about.Ui_AboutDialog, QDialog):
with open('/etc/qubes-release', 'r') as release_file:
self.release.setText(release_file.read())
- self.connect(self.ok, SIGNAL("clicked()"), SLOT("accept()"))
- self.connect(self.releaseNotes, SIGNAL("clicked()"),
- self.on_release_notes_clicked)
- self.connect(self.informationNotes, SIGNAL("clicked()"),
- self.on_information_notes_clicked)
+ self.ok.clicked.connect(self.accept)
+ self.releaseNotes.clicked.connect(on_release_notes_clicked)
+ self.informationNotes.clicked.connect(on_information_notes_clicked)
- def on_release_notes_clicked(self):
- release_notes_dialog = ReleaseNotesDialog()
- release_notes_dialog.exec_()
- self.accept()
+def on_release_notes_clicked():
+ release_notes_dialog = ReleaseNotesDialog()
+ release_notes_dialog.exec_()
- def on_information_notes_clicked(self):
- information_notes_dialog = InformationNotesDialog()
- information_notes_dialog.exec_()
- self.accept()
+def on_information_notes_clicked():
+ information_notes_dialog = InformationNotesDialog()
+ information_notes_dialog.exec_()
diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py
index d974842..358f1be 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 time
+import asyncio
+from contextlib import suppress
+
+# pylint: disable=too-few-public-methods
+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,7 @@ 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.thread = None
self.setupUi(self)
@@ -112,6 +133,16 @@ 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):
+ 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.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)
+ self.thread.wait()
+ 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..4f35539
--- /dev/null
+++ b/qubesmanager/common_threads.py
@@ -0,0 +1,54 @@
+#!/usr/bin/python3
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2018 Donoban
+#
+# 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
+
+
+# pylint: disable=too-few-public-methods
+class RemoveVMThread(QtCore.QThread):
+ def __init__(self, vm):
+ QtCore.QThread.__init__(self)
+ self.vm = vm
+ self.msg = None
+
+ def run(self):
+ try:
+ del self.vm.app.domains[self.vm.name]
+ except (exc.QubesException, KeyError) as ex:
+ self.msg = ("Error removing qube!", str(ex))
+
+
+# pylint: disable=too-few-public-methods
+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
+ self.msg = None
+
+ def run(self):
+ try:
+ self.src_vm.app.clone_vm(self.src_vm, self.dst_name)
+ self.msg = ("Sucess", "The qube was cloned sucessfully.")
+ except exc.QubesException as ex:
+ self.msg = ("Error while cloning qube!", str(ex))
diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py
index 6e7fa52..5949833 100644
--- a/qubesmanager/create_new_vm.py
+++ b/qubesmanager/create_new_vm.py
@@ -22,8 +22,6 @@
#
import sys
-import threading
-import time
import subprocess
from PyQt4 import QtCore, QtGui # pylint: disable=import-error
@@ -35,7 +33,40 @@ import qubesadmin.exc
from . import utils
from .ui_newappvmdlg import Ui_NewVMDlg # pylint: disable=import-error
-from .thread_monitor import ThreadMonitor
+
+# pylint: disable=too-few-public-methods
+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.msg = 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.msg = str(qex)
+ except Exception as ex: # pylint: disable=broad-except
+ self.msg = repr(ex)
class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
@@ -46,6 +77,9 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
self.qtapp = qtapp
self.app = app
+ self.thread = None
+ self.progress = None
+
# Theoretically we should be locking for writing here and unlock
# only after the VM creation finished. But the code would be
# more messy...
@@ -125,67 +159,37 @@ 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.msg:
QtGui.QMessageBox.warning(None,
self.tr("Error creating the qube!"),
- self.tr("ERROR: {}").format(thread_monitor.error_msg))
+ self.tr("ERROR: {}").format(self.thread.msg))
self.done(0)
- if thread_monitor.success:
+ if not self.thread.msg:
if self.launch_settings.isChecked():
- subprocess.check_call(['qubes-vm-settings', name])
+ subprocess.check_call(['qubes-vm-settings',
+ str(self.name.text())])
if self.install_system.isChecked():
subprocess.check_call(
- ['qubes-vm-boot-from-device', name])
+ ['qubes-vm-boot-from-device', str(self.name.text())])
- @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 7babde8..1693064 100644
--- a/qubesmanager/qube_manager.py
+++ b/qubesmanager/qube_manager.py
@@ -25,13 +25,12 @@ import sys
import os
import os.path
import subprocess
-import time
from datetime import datetime, timedelta
import traceback
-import threading
+from contextlib import suppress
+
import quamash
import asyncio
-from contextlib import suppress
from qubesadmin import Qubes
from qubesadmin import exc
@@ -44,14 +43,15 @@ from PyQt4 import QtCore # pylint: disable=import-error
from qubesmanager.about import AboutDialog
from . import ui_qubemanager # pylint: disable=no-name-in-module
-from . import thread_monitor
from . import table_widgets
from . import settings
from . import global_settings
from . import restore
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):
@@ -209,7 +209,7 @@ class VmShutdownMonitor(QtCore.QObject):
self.tr(
"The Qube '{0}' hasn't shutdown within the last "
"{1} seconds, do you want to kill it?
").format(
- vm.name, self.shutdown_time / 1000),
+ vm.name, self.shutdown_time / 1000),
self.tr("Kill it!"),
self.tr("Wait another {0} seconds...").format(
self.shutdown_time / 1000))
@@ -238,6 +238,56 @@ class VmShutdownMonitor(QtCore.QObject):
self.restart_vm_if_needed()
+# pylint: disable=too-few-public-methods
+class StartVMThread(QtCore.QThread):
+ def __init__(self, vm):
+ QtCore.QThread.__init__(self)
+ self.vm = vm
+ self.msg = None
+
+ def run(self):
+ try:
+ self.vm.start()
+ except exc.QubesException as ex:
+ self.msg = ("Error starting Qube!", str(ex))
+
+
+# pylint: disable=too-few-public-methods
+class UpdateVMThread(QtCore.QThread):
+ def __init__(self, vm):
+ QtCore.QThread.__init__(self)
+ self.vm = vm
+ self.msg = None
+
+ def run(self):
+ try:
+ if self.vm.qid == 0:
+ subprocess.check_call(
+ ["/usr/bin/qubes-dom0-update", "--clean", "--gui"])
+ else:
+ if not self.vm.is_running():
+ self.vm.start()
+ self.vm.run_service("qubes.InstallUpdatesGUI",\
+ user="root", wait=False)
+ except (ChildProcessError, exc.QubesException) as ex:
+ self.msg = ("Error on qube update!", str(ex))
+
+
+# pylint: disable=too-few-public-methods
+class RunCommandThread(QtCore.QThread):
+ def __init__(self, vm, command_to_run):
+ QtCore.QThread.__init__(self)
+ self.vm = vm
+ self.command_to_run = command_to_run
+ self.msg = None
+
+ def run(self):
+ try:
+ self.vm.run(self.command_to_run)
+ except (ChildProcessError, exc.QubesException) as ex:
+ self.msg = ("Error while running command!", str(ex))
+
+
class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
# pylint: disable=too-many-instance-attributes
row_height = 30
@@ -256,7 +306,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
@@ -398,7 +448,11 @@ 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)
@@ -410,12 +464,40 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
dispatcher.add_handler('property-load',
self.on_domain_changed)
+ # It needs to store threads until they finish
+ self.threads_list = []
+ self.progress = None
+
# Check Updates Timer
timer = QtCore.QTimer(self)
timer.timeout.connect(self.check_updates)
timer.start(1000 * 30) # 30s
self.check_updates()
+ def keyPressEvent(self, event): # pylint: disable=invalid-name
+ if event.key() == QtCore.Qt.Key_Escape:
+ self.searchbox.clear()
+ super(VmManagerWindow, self).keyPressEvent(event)
+
+ def clear_threads(self):
+ for thread in self.threads_list:
+ if thread.isFinished():
+ if self.progress:
+ self.progress.hide()
+ self.progress = None
+
+ if thread.msg:
+ (title, msg) = thread.msg
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr(title),
+ self.tr(msg))
+
+ self.threads_list.remove(thread)
+ return
+
+ raise RuntimeError('No finished thread found')
+
def closeEvent(self, event):
# pylint: disable=invalid-name
# save window size at close
@@ -433,21 +515,19 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
pass
def on_domain_added(self, _submitter, _event, vm, **_kwargs):
+ row_no = 0
self.table.setSortingEnabled(False)
-
- row_no = self.table.rowCount()
- self.table.setRowCount(row_no + 1)
-
- for domain in self.qubes_app.domains:
- if domain == vm:
- vm_row = VmRowInTable(domain, row_no, self.table)
- self.vms_in_table[domain.qid] = vm_row
- self.table.setSortingEnabled(True)
- self.showhide_vms()
- return
-
- # Never should reach here
- raise RuntimeError('Added domain not found')
+ try:
+ domain = self.qubes_app.domains[vm]
+ row_no = self.table.rowCount()
+ self.table.setRowCount(row_no + 1)
+ vm_row = VmRowInTable(domain, row_no, self.table)
+ self.vms_in_table[domain.qid] = vm_row
+ except (exc.QubesException, KeyError):
+ if row_no != 0:
+ self.table.removeRow(row_no)
+ self.table.setSortingEnabled(True)
+ self.showhide_vms()
def on_domain_removed(self, _submitter, _event, **kwargs):
row_to_delete = None
@@ -520,17 +600,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
return [vm for vm in self.qubes_app.domains]
def fill_table(self):
- progress = QtGui.QProgressDialog(
- self.tr(
- "Loading Qube Manager..."), "", 0, 0)
- progress.setWindowTitle(self.tr("Qube Manager"))
- progress.setWindowFlags(QtCore.Qt.Window |
- QtCore.Qt.WindowTitleHint |
- QtCore.Qt.CustomizeWindowHint)
- progress.setCancelButton(None)
- progress.setModal(True)
- progress.show()
-
self.table.setSortingEnabled(False)
vms_list = self.get_vms_list()
@@ -538,19 +607,26 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.table.setRowCount(len(vms_list))
+ progress = QtGui.QProgressDialog(
+ self.tr(
+ "Loading Qube Manager..."), "", 0, len(vms_list))
+ progress.setWindowTitle(self.tr("Qube Manager"))
+ progress.setMinimumDuration(1000)
+ progress.setCancelButton(None)
+
row_no = 0
for vm in vms_list:
+ progress.setValue(row_no)
vm_row = VmRowInTable(vm, row_no, self.table)
vms_in_table[vm.qid] = vm_row
row_no += 1
- self.qt_app.processEvents()
+
+ progress.setValue(row_no)
self.vms_list = vms_list
self.vms_in_table = vms_in_table
self.table.setSortingEnabled(True)
- progress.hide()
-
def showhide_vms(self):
if not self.search:
for row_no in range(self.table.rowCount()):
@@ -584,11 +660,9 @@ 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:
-
# TODO: add boot from device to menu and add windows tools there
# Update available actions:
self.action_settings.setEnabled(vm.klass != 'AdminVM')
@@ -640,7 +714,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_createvm_triggered')
def action_createvm_triggered(self): # pylint: disable=no-self-use
- subprocess.check_call('qubes-vm-create')
+ create_window = create_new_vm.NewVmDlg(self.qt_app, self.qubes_app)
+ create_window.exec_()
def get_selected_vm(self):
# vm selection relies on the VmInfo widget's value used
@@ -681,7 +756,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
"or setting that uses it.").format(list_text))
info_dialog.setModal(False)
info_dialog.show()
- self.qt_app.processEvents()
return
@@ -708,44 +782,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
else:
# remove the VM
- t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=self.do_remove_vm,
- args=(vm, self.qubes_app, t_monitor))
- thread.daemon = True
+ thread = common_threads.RemoveVMThread(vm)
+ self.threads_list.append(thread)
+ thread.finished.connect(self.clear_threads)
thread.start()
- progress = QtGui.QProgressDialog(
- self.tr(
- "Removing Qube: {0}...").format(vm.name), "", 0, 0)
- progress.setWindowFlags(QtCore.Qt.Window |
- QtCore.Qt.WindowTitleHint |
- QtCore.Qt.CustomizeWindowHint)
- progress.setCancelButton(None)
- progress.setModal(True)
- progress.show()
-
- while not t_monitor.is_finished():
- self.qt_app.processEvents()
- time.sleep(0.1)
-
- progress.hide()
-
- if t_monitor.success:
- pass
- else:
- QtGui.QMessageBox.warning(None, self.tr("Error removing Qube!"),
- self.tr("ERROR: {0}").format(
- t_monitor.error_msg))
-
- @staticmethod
- def do_remove_vm(vm, qubes_app, t_monitor):
- try:
- del qubes_app.domains[vm.name]
- except exc.QubesException as ex:
- t_monitor.set_error_msg(str(ex))
-
- t_monitor.set_finished()
-
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_clonevm_triggered')
def action_clonevm_triggered(self):
@@ -763,48 +804,18 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if not ok or clone_name == "":
return
- t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=self.do_clone_vm,
- args=(vm, self.qubes_app,
- clone_name, t_monitor))
- thread.daemon = True
+ self.progress = QtGui.QProgressDialog(
+ self.tr(
+ "Cloning Qube..."), "", 0, 0)
+ self.progress.setCancelButton(None)
+ self.progress.setModal(True)
+ self.progress.show()
+
+ thread = common_threads.CloneVMThread(vm, clone_name)
+ thread.finished.connect(self.clear_threads)
+ self.threads_list.append(thread)
thread.start()
- progress = QtGui.QProgressDialog(
- self.tr("Cloning Qube {0} to {1}...").format(
- vm.name, clone_name), "", 0, 0)
- progress.setWindowFlags(QtCore.Qt.Window |
- QtCore.Qt.WindowTitleHint |
- QtCore.Qt.CustomizeWindowHint)
- progress.setCancelButton(None)
- progress.setModal(True)
- progress.show()
-
- while not t_monitor.is_finished():
- self.qt_app.processEvents()
- time.sleep(0.2)
-
- progress.hide()
-
- if not t_monitor.success:
- QtGui.QMessageBox.warning(
- None,
- self.tr("Error while cloning Qube"),
- self.tr("Exception while cloning:
{0}").format(
- t_monitor.error_msg))
-
-
- @staticmethod
- def do_clone_vm(src_vm, qubes_app, dst_name, t_monitor):
- dst_vm = None
- try:
- dst_vm = qubes_app.clone_vm(src_vm, dst_name)
- except exc.QubesException as ex:
- t_monitor.set_error_msg(str(ex))
- if dst_vm:
- pass
- t_monitor.set_finished()
-
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_resumevm_triggered')
def action_resumevm_triggered(self):
@@ -813,8 +824,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!"),
@@ -826,34 +835,12 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
def start_vm(self, vm):
if vm.is_running():
return
- t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=self.do_start_vm,
- args=(vm, t_monitor))
- thread.daemon = True
+
+ thread = StartVMThread(vm)
+ self.threads_list.append(thread)
+ thread.finished.connect(self.clear_threads)
thread.start()
- while not t_monitor.is_finished():
- self.qt_app.processEvents()
- time.sleep(0.1)
-
- if not t_monitor.success:
- QtGui.QMessageBox.warning(
- None,
- self.tr("Error starting Qube!"),
- self.tr("ERROR: {0}").format(t_monitor.error_msg))
-
-
- @staticmethod
- def do_start_vm(vm, t_monitor):
- try:
- vm.start()
- except exc.QubesException as ex:
- t_monitor.set_error_msg(str(ex))
- t_monitor.set_finished()
- return
-
- t_monitor.set_finished()
-
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_startvm_tools_install_triggered')
# TODO: replace with boot from device
@@ -866,8 +853,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
vm = self.get_selected_vm()
try:
vm.pause()
- self.vms_in_table[vm.qid].update()
- self.table_selection_changed()
except exc.QubesException as ex:
QtGui.QMessageBox.warning(
None,
@@ -885,9 +870,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.tr("Are you sure you want to power down the Qube"
" '{0}'?
This will shutdown all the "
"running applications within this Qube.").format(
- vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
-
- self.qt_app.processEvents()
+ vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
if reply == QtGui.QMessageBox.Yes:
self.shutdown_vm(vm)
@@ -922,8 +905,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
"applications within this Qube.").format(vm.name),
QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
- self.qt_app.processEvents()
-
if reply == QtGui.QMessageBox.Yes:
# in case the user shut down the VM in the meantime
if vm.is_running():
@@ -938,7 +919,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if not (vm.is_running() or vm.is_paused()):
info = self.tr("Qube '{0}' is not running. Are you "
"absolutely sure you want to try to kill it?
"
- "This will end (not shutdown!) all "
+ "This will end (not shutdown!) all "
"the running applications within this "
"Qube.").format(vm.name)
else:
@@ -952,8 +933,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel,
QtGui.QMessageBox.Cancel)
- self.qt_app.processEvents()
-
if reply == QtGui.QMessageBox.Yes:
try:
vm.kill()
@@ -1017,55 +996,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if reply != QtGui.QMessageBox.Yes:
return
- self.qt_app.processEvents()
-
- t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=self.do_update_vm,
- args=(vm, t_monitor))
- thread.daemon = True
+ thread = UpdateVMThread(vm)
+ self.threads_list.append(thread)
+ thread.finished.connect(self.clear_threads)
thread.start()
- progress = QtGui.QProgressDialog(
- self.tr(
- "{0}
Please wait for the updater to "
- "launch...").format(vm.name), "", 0, 0)
- progress.setWindowFlags(QtCore.Qt.Window |
- QtCore.Qt.WindowTitleHint |
- QtCore.Qt.CustomizeWindowHint)
- progress.setCancelButton(None)
- progress.setModal(True)
- progress.show()
-
- while not t_monitor.is_finished():
- self.qt_app.processEvents()
- time.sleep(0.2)
-
- progress.hide()
-
- if vm.qid != 0:
- if not t_monitor.success:
- QtGui.QMessageBox.warning(
- None,
- self.tr("Error on Qube update!"),
- self.tr("ERROR: {0}").format(t_monitor.error_msg))
-
-
- @staticmethod
- def do_update_vm(vm, t_monitor):
- try:
- if vm.qid == 0:
- subprocess.check_call(
- ["/usr/bin/qubes-dom0-update", "--clean", "--gui"])
- else:
- if not vm.is_running():
- vm.start()
- vm.run_service("qubes.InstallUpdatesGUI",
- user="root", wait=False)
- except (ChildProcessError, exc.QubesException) as ex:
- t_monitor.set_error_msg(str(ex))
- t_monitor.set_finished()
- return
- t_monitor.set_finished()
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_run_command_in_vm_triggered')
@@ -1078,30 +1013,12 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.tr('Run command in {}:').format(vm.name))
if not ok or command_to_run == "":
return
- t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=self.do_run_command_in_vm, args=(
- vm, command_to_run, t_monitor))
- thread.daemon = True
+
+ thread = RunCommandThread(vm, command_to_run)
+ self.threads_list.append(thread)
+ thread.finished.connect(self.clear_threads)
thread.start()
- while not t_monitor.is_finished():
- self.qt_app.processEvents()
- time.sleep(0.2)
-
- if not t_monitor.success:
- QtGui.QMessageBox.warning(
- None, self.tr("Error while running command"),
- self.tr("Exception while running command:
{0}").format(
- t_monitor.error_msg))
-
- @staticmethod
- def do_run_command_in_vm(vm, command_to_run, t_monitor):
- try:
- vm.run(command_to_run)
- except (ChildProcessError, exc.QubesException) as ex:
- t_monitor.set_error_msg(str(ex))
- t_monitor.set_finished()
-
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_set_keyboard_layout_triggered')
def action_set_keyboard_layout_triggered(self):
@@ -1141,8 +1058,9 @@ 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.exec_()
+ backup_window = backup.BackupVMsWindow(self.qt_app, self.qubes_app,
+ self.dispatcher, self)
+ backup_window.show()
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_exit_triggered')
diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py
index 18e628c..0c1d07c 100644
--- a/qubesmanager/restore.py
+++ b/qubesmanager/restore.py
@@ -23,31 +23,56 @@
import sys
from PyQt4 import QtCore # pylint: disable=import-error
from PyQt4 import QtGui # pylint: disable=import-error
-import threading
-import time
import os
import os.path
import traceback
import logging
import logging.handlers
-import signal
-
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 import Queue
from multiprocessing.queues import Empty
from qubesadmin import Qubes, exc
from qubesadmin.backup import restore
-class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
+# pylint: disable=too-few-public-methods
+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.msg = None
+ self.canceled = 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.msg = '\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)
@@ -57,6 +82,8 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
self.vms_to_restore = None
self.func_output = []
+ self.thread = None
+
# Set up logging
self.feedback_queue = Queue()
handler = logging.handlers.QueueHandler(self.feedback_queue)
@@ -64,9 +91,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
@@ -144,41 +168,12 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
self.select_vms_widget.available_list.addItem(vmname)
except exc.QubesException as ex:
QtGui.QMessageBox.warning(None, self.tr("Restore error!"), str(ex))
+ self.restart()
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)
if self.currentPage() is self.select_vms_page:
self.__fill_vms_list__()
@@ -210,51 +205,56 @@ 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.msg:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Restore qubes"),
+ self.tr(self.thread.msg))
+
+ 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)
+
+ 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,7 +265,7 @@ 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...")))
diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py
index 5391ad4..363c5f4 100755
--- a/qubesmanager/settings.py
+++ b/qubesmanager/settings.py
@@ -27,8 +27,6 @@ import os.path
import os
import re
import subprocess
-import threading
-import time
import traceback
import sys
from qubesadmin.tools import QubesArgumentParser
@@ -38,7 +36,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
@@ -47,16 +45,90 @@ from PyQt4 import QtCore, QtGui # pylint: disable=import-error
from . import ui_settingsdlg # pylint: disable=no-name-in-module
+# pylint: disable=too-few-public-methods
+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
+ self.msg = None
+
+ 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 qubesadmin.exc.QubesException:
+ 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, self.vm.name, self.vm.name)\
+ + list_text)
+
+ except qubesadmin.exc.QubesException as ex:
+ self.msg = ("Rename error!", str(ex))
+ except Exception as ex: # pylint: disable=broad-except
+ self.msg = ("Rename error!", repr(ex))
+
+
+# pylint: disable=too-few-public-methods
+class RefreshAppsVMThread(QtCore.QThread):
+ def __init__(self, vm):
+ QtCore.QThread.__init__(self)
+ self.vm = vm
+ self.msg = None
+
+ 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: # pylint: disable=broad-except
+ self.msg = ("Refresh failed!", str(ex))
+
# pylint: disable=too-many-instance-attributes
class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
tabs_indices = collections.OrderedDict((
- ('basic', 0),
- ('advanced', 1),
- ('firewall', 2),
- ('devices', 3),
- ('applications', 4),
- ('services', 5),
+ ('basic', 0),
+ ('advanced', 1),
+ ('firewall', 2),
+ ('devices', 3),
+ ('applications', 4),
+ ('services', 5),
))
def __init__(self, vm, qapp, init_page="basic", parent=None):
@@ -64,6 +136,9 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.vm = vm
self.qapp = qapp
+ self.threads_list = []
+ self.progress = None
+ self.thread_closes = False
try:
self.source_vm = self.vm.template
except AttributeError:
@@ -149,6 +224,29 @@ 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 self.progress:
+ self.progress.hide()
+ self.progress = None
+
+ if thread.msg:
+ (title, msg) = thread.msg
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr(title),
+ self.tr(msg))
+
+ self.threads_list.remove(thread)
+
+ if self.thread_closes:
+ self.done(0)
+
+ return
+
+ raise RuntimeError('No finished thread found')
+
def keyPressEvent(self, event): # pylint: disable=invalid-name
if event.key() == QtCore.Qt.Key_Enter \
or event.key() == QtCore.Qt.Key_Return:
@@ -163,31 +261,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 while changing settings for {0}!"\
+ ).format(self.vm.name),\
+ self.tr("ERROR: {0}").format('\n'.join(error)))
def apply(self):
self.save_changes()
@@ -196,9 +277,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:
@@ -236,12 +317,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
@@ -259,18 +336,17 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
'please enable networking.')
)
if netvm is not None and \
- not netvm.features.check_with_template(
- 'qubes-firewall',
- False):
+ not netvm.features.check_with_template(\
+ 'qubes-firewall', False):
QtGui.QMessageBox.warning(
self,
self.tr("Qube configuration problem!"),
- self.tr("The '{vm}' qube is network connected to "
- "'{netvm}', which does not support firewall!
"
- "You may edit the '{vm}' qube firewall rules, but "
- "these will not take any effect until you connect it "
- "to a working Firewall qube.").format(
- vm=self.vm.name, netvm=netvm.name))
+ self.tr("The '{vm}' qube is network connected to "\
+ "'{netvm}', which does not support firewall!
"\
+ "You may edit the '{vm}' qube firewall rules, but "\
+ "these will not take any effect until you connect it "\
+ "to a working Firewall qube.").format(\
+ vm=self.vm.name, netvm=netvm.name))
def current_tab_changed(self, idx):
if idx == self.tabs_indices["firewall"]:
@@ -463,61 +539,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)
@@ -543,19 +564,19 @@ 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)
+ thread = RenameVMThread(self.vm, new_vm_name, dependencies)
+ self.threads_list.append(thread)
+ thread.finished.connect(self.clear_threads)
- def _remove_vm(self, t_monitor):
- try:
- del self.vm.app.domains[self.vm.name]
+ self.progress = QtGui.QProgressDialog(
+ self.tr(
+ "Renaming Qube..."), "", 0, 0)
+ self.progress.setCancelButton(None)
+ self.progress.setModal(True)
+ self.thread_closes = True
+ self.progress.show()
- 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.start()
def remove_vm(self):
@@ -583,7 +604,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:
@@ -592,17 +614,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr("Removal cancelled"),
self.tr("The qube will not be removed."))
- def _clone_vm(self, t_monitor, name):
- try:
- self.vm.app.clone_vm(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()
-
def clone_vm(self):
cloned_vm_name, ok = QtGui.QInputDialog.getText(
@@ -611,11 +622,19 @@ 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)
+
+ self.progress = QtGui.QProgressDialog(
+ self.tr(
+ "Cloning Qube..."), "", 0, 0)
+ self.progress.setCancelButton(None)
+ self.progress.setModal(True)
+ self.thread_closes = True
+ self.progress.show()
+
+ thread.start()
######### advanced tab
@@ -766,8 +785,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.virt_mode.clear()
# pylint: disable=attribute-defined-outside-init
- self.virt_mode_list, self.virt_mode_idx = utils.prepare_choice(
- self.virt_mode, self.vm, 'virt_mode', choices, None,
+ self.virt_mode_list, self.virt_mode_idx = utils.prepare_choice(\
+ self.virt_mode, self.vm, 'virt_mode', choices, None,\
allow_default=True, transform=(lambda x: str(x).upper()))
if old_mode is not None:
@@ -958,43 +977,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()
diff --git a/rpm_spec/qmgr.spec.in b/rpm_spec/qmgr.spec.in
index 554eeb0..b00e42e 100644
--- a/rpm_spec/qmgr.spec.in
+++ b/rpm_spec/qmgr.spec.in
@@ -89,7 +89,7 @@ rm -rf $RPM_BUILD_ROOT
%{python3_sitelib}/qubesmanager/releasenotes.py
%{python3_sitelib}/qubesmanager/informationnotes.py
%{python3_sitelib}/qubesmanager/create_new_vm.py
-%{python3_sitelib}/qubesmanager/thread_monitor.py
+%{python3_sitelib}/qubesmanager/common_threads.py
%{python3_sitelib}/qubesmanager/qube_manager.py
%{python3_sitelib}/qubesmanager/utils.py
%{python3_sitelib}/qubesmanager/bootfromdevice.py