Migration from python threads and to QThread obj

Simple design and better performance, just start() thread and finished
call back will show any error or msg, stop progress dialog and do cleanup.

Full remove of QT process_events() calls.
This commit is contained in:
donoban 2018-10-20 18:34:15 +02:00
parent 35cdf5e1e5
commit 29ca4eb3a8
No known key found for this signature in database
GPG Key ID: 141310D8E3ED08A5
7 changed files with 398 additions and 419 deletions

View File

@ -23,9 +23,11 @@
import traceback import traceback
import signal import signal
import quamash
from qubesadmin import Qubes, exc from qubesadmin import Qubes, exc
from qubesadmin import utils as admin_utils from qubesadmin import utils as admin_utils
from qubesadmin import events
from qubes.storage.file import get_disk_usage from qubes.storage.file import get_disk_usage
from PyQt4 import QtCore # pylint: disable=import-error from PyQt4 import QtCore # pylint: disable=import-error
@ -35,18 +37,38 @@ from . import multiselectwidget
from . import backup_utils from . import backup_utils
from . import utils from . import utils
import grp import grp
import pwd import pwd
import sys import sys
import os import os
from . import thread_monitor import asyncio
import threading from contextlib import suppress
import time 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): class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
def __init__(self, qt_app, qubes_app, dispatcher, parent=None):
def __init__(self, qt_app, qubes_app, parent=None):
super(BackupVMsWindow, self).__init__(parent) super(BackupVMsWindow, self).__init__(parent)
self.qt_app = qt_app self.qt_app = qt_app
@ -54,8 +76,6 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
self.backup_settings = QtCore.QSettings() self.backup_settings = QtCore.QSettings()
self.selected_vms = [] self.selected_vms = []
self.canceled = False
self.thread_monitor = None
self.setupUi(self) self.setupUi(self)
@ -112,6 +132,17 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
selected = self.load_settings() selected = self.load_settings()
self.__fill_vms_list__(selected) 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): def load_settings(self):
""" """
Helper function that tries to load existing backup profile Helper function that tries to load existing backup profile
@ -260,24 +291,6 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
return True 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 @staticmethod
def cleanup_temporary_files(): def cleanup_temporary_files():
@ -310,33 +323,27 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
self.showFileDialog.setChecked(self.showFileDialog.isEnabled() self.showFileDialog.setChecked(self.showFileDialog.isEnabled()
and str(self.dir_line_edit.text()) and str(self.dir_line_edit.text())
.count("media/") > 0) .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(): vm = self.qubes_app.domains[
self.qt_app.processEvents() self.appvm_combobox.currentText()]
time.sleep(0.1)
if not self.thread_monitor.success: self.thread = BackupThread(vm)
if self.canceled: self.thread.finished.connect(self.backup_finished)
self.progress_status.setText( self.thread.start()
self.tr(
"Backup aborted. " signal.signal(signal.SIGCHLD, old_sigchld_handler)
"Temporary file may be left at backup location."))
else: def backup_finished(self):
if self.thread.msg:
self.progress_status.setText(self.tr("Backup error.")) self.progress_status.setText(self.tr("Backup error."))
QtGui.QMessageBox.warning( QtGui.QMessageBox.warning(
self, self.tr("Backup error!"), self, self.tr("Backup error!"),
self.tr("ERROR: {}").format( self.tr("ERROR: {}").format(
self.thread_monitor.error_msg)) self.thread.msg))
else: else:
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(100) self.progress_bar.setValue(100)
self.progress_status.setText(self.tr("Backup finished.")) self.progress_status.setText(self.tr("Backup finished."))
if self.showFileDialog.isChecked(): if self.showFileDialog.isChecked():
orig_text = self.progress_status.text orig_text = self.progress_status.text
self.progress_status.setText( self.progress_status.setText(
@ -344,29 +351,26 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
" Please unmount your backup volume and cancel " " Please unmount your backup volume and cancel "
"the file selection dialog.")) "the file selection dialog."))
backup_utils.select_path_button_clicked(self, False, True) backup_utils.select_path_button_clicked(self, False, True)
self.button(self.CancelButton).setEnabled(False) self.button(self.CancelButton).setEnabled(False)
self.button(self.FinishButton).setEnabled(True) self.button(self.FinishButton).setEnabled(True)
self.showFileDialog.setEnabled(False) self.showFileDialog.setEnabled(False)
self.cleanup_temporary_files() self.cleanup_temporary_files()
# turn off only when backup was successful # turn off only when backup was successful
if self.thread_monitor.success and \ if self.turn_off_checkbox.isChecked():
self.turn_off_checkbox.isChecked():
os.system('systemctl poweroff') os.system('systemctl poweroff')
signal.signal(signal.SIGCHLD, old_sigchld_handler)
def reject(self): def reject(self):
if self.currentPage() is self.commit_page: if self.currentPage() is self.commit_page:
self.canceled = True self.thread.terminate()
self.qubes_app.qubesd_call( self.qubes_app.qubesd_call(
'dom0', 'admin.backup.Cancel', 'dom0', 'admin.backup.Cancel',
backup_utils.get_profile_name(True)) backup_utils.get_profile_name(True))
self.progress_bar.setMaximum(100) QtGui.QMessageBox.warning(
self.progress_bar.setValue(0) self, self.tr("Backup aborted!"),
self.button(self.CancelButton).setDisabled(True) self.tr("ERROR: {}").format("Aborted!"))
self.cleanup_temporary_files()
else:
self.cleanup_temporary_files() self.cleanup_temporary_files()
self.done(0) self.done(0)
@ -402,9 +406,14 @@ def handle_exception(exc_type, exc_value, exc_traceback):
error + "at <b>line %d</b> of file <b>%s</b>.<br/><br/>" error + "at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
% (line, filename)) % (line, filename))
def loop_shutdown():
pending = asyncio.Task.all_tasks()
for task in pending:
with suppress(asyncio.CancelledError):
task.cancel()
def main(): def main():
qt_app = QtGui.QApplication(sys.argv) qt_app = QtGui.QApplication(sys.argv)
qt_app.setOrganizationName("The Qubes Project") qt_app.setOrganizationName("The Qubes Project")
qt_app.setOrganizationDomain("http://qubes-os.org") qt_app.setOrganizationDomain("http://qubes-os.org")
@ -412,14 +421,24 @@ def main():
sys.excepthook = handle_exception 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() backup_window.show()
qt_app.exec_() try:
qt_app.exit() 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__": if __name__ == "__main__":

View File

@ -0,0 +1,51 @@
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2011 Marek Marczykowski <marmarek@mimuw.edu.pl>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
#
#
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))

View File

@ -35,7 +35,39 @@ import qubesadmin.exc
from . import utils from . import utils
from .ui_newappvmdlg import Ui_NewVMDlg # pylint: disable=import-error 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): class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
@ -125,67 +157,36 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
properties['virt_mode'] = 'hvm' properties['virt_mode'] = 'hvm'
properties['kernel'] = None properties['kernel'] = None
thread_monitor = ThreadMonitor() self.thread = CreateVMThread(self.app, vmclass, name, label,
thread = threading.Thread(target=self.do_create_vm, template, properties)
args=(self.app, vmclass, name, label, template, properties, self.thread.finished.connect(self.create_finished)
thread_monitor)) self.thread.start()
thread.daemon = True
thread.start()
progress = QtGui.QProgressDialog( self.progress = QtGui.QProgressDialog(
self.tr("Creating new qube <b>{}</b>...").format(name), "", 0, 0) self.tr("Creating new qube <b>{}</b>...").format(name), "", 0, 0)
progress.setCancelButton(None) self.progress.setCancelButton(None)
progress.setModal(True) self.progress.setModal(True)
progress.show() self.progress.show()
while not thread_monitor.is_finished(): def create_finished(self):
self.qtapp.processEvents() self.progress.hide()
time.sleep(0.1)
progress.hide() if self.thread.error:
if not thread_monitor.success:
QtGui.QMessageBox.warning(None, QtGui.QMessageBox.warning(None,
self.tr("Error creating the qube!"), self.tr("Error creating the qube!"),
self.tr("ERROR: {}").format(thread_monitor.error_msg)) self.tr("ERROR: {}").format(thread.error))
self.done(0) self.done(0)
if thread_monitor.success: if not self.thread.error:
if self.launch_settings.isChecked(): 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(): if self.install_system.isChecked():
subprocess.check_call( 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): def type_change(self):
# AppVM # AppVM
if self.vm_type.currentIndex() == 0: if self.vm_type.currentIndex() == 0:
self.template_vm.setEnabled(True) self.template_vm.setEnabled(True)

View File

@ -25,12 +25,13 @@ import sys
import os import os
import os.path import os.path
import subprocess import subprocess
import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
import traceback import traceback
from contextlib import suppress
import time
import quamash import quamash
import asyncio import asyncio
from contextlib import suppress
from qubesadmin import Qubes from qubesadmin import Qubes
from qubesadmin import exc from qubesadmin import exc
@ -51,6 +52,7 @@ from . import backup
from . import create_new_vm from . import create_new_vm
from . import log_dialog from . import log_dialog
from . import utils as manager_utils from . import utils as manager_utils
from . import common_threads
class SearchBox(QtGui.QLineEdit): class SearchBox(QtGui.QLineEdit):
@ -241,32 +243,20 @@ class StartVMThread(QtCore.QThread):
def __init__(self, vm): def __init__(self, vm):
QtCore.QThread.__init__(self) QtCore.QThread.__init__(self)
self.vm = vm self.vm = vm
self.error = None
def run(self): def run(self):
try: try:
self.vm.start() self.vm.start()
except exc.QubesException as ex: except exc.QubesException as ex:
self.emit(QtCore.SIGNAL('show_error(QString, QString)'),\ self.error = ("Error starting Qube!", str(ex))
"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): class UpdateVMThread(QtCore.QThread):
def __init__(self, vm): def __init__(self, vm):
QtCore.QThread.__init__(self) QtCore.QThread.__init__(self)
self.vm = vm self.vm = vm
self.error = None
def run(self): def run(self):
try: try:
@ -279,23 +269,7 @@ class UpdateVMThread(QtCore.QThread):
self.vm.run_service("qubes.InstallUpdatesGUI",\ self.vm.run_service("qubes.InstallUpdatesGUI",\
user="root", wait=False) user="root", wait=False)
except (ChildProcessError, exc.QubesException) as ex: except (ChildProcessError, exc.QubesException) as ex:
self.emit(QtCore.SIGNAL('show_error(QString, QString)'),\ self.error = ("Error on Qube update!", str(ex))
"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))
class RunCommandThread(QtCore.QThread): class RunCommandThread(QtCore.QThread):
@ -308,8 +282,7 @@ class RunCommandThread(QtCore.QThread):
try: try:
self.vm.run(self.command_to_run) self.vm.run(self.command_to_run)
except (ChildProcessError, exc.QubesException) as ex: except (ChildProcessError, exc.QubesException) as ex:
self.emit(QtCore.SIGNAL('show_error(QString, QString)'),\ self.error = ("Error while running command!", str(ex))
"Error while running command!", str(ex))
class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
@ -472,7 +445,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
dispatcher.add_handler('domain-start-failed', dispatcher.add_handler('domain-start-failed',
self.on_domain_status_changed) self.on_domain_status_changed)
dispatcher.add_handler('domain-stopped', 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-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-add', self.on_domain_added)
dispatcher.add_handler('domain-delete', self.on_domain_removed) 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): def clear_threads(self):
for thread in self.threads_list: for thread in self.threads_list:
if thread.isFinished(): 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) self.threads_list.remove(thread)
def closeEvent(self, event): def closeEvent(self, event):
@ -659,7 +641,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.manager_settings.sync() self.manager_settings.sync()
def table_selection_changed(self): def table_selection_changed(self):
vm = self.get_selected_vm() vm = self.get_selected_vm()
if vm is not None and vm in self.qubes_app.domains: 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: else:
# remove the VM # remove the VM
thread = RemoveVMThread(vm, self.qubes_app) thread = common_threads.RemoveVMThread(vm)
self.threads_list.append(thread) self.threads_list.append(thread)
self.connect(thread, QtCore.SIGNAL("show_error(QString, QString)"), self.show_error) self.connect(thread, QtCore.SIGNAL("show_error(QString, QString)"), self.show_error)
thread.finished.connect(self.clear_threads) thread.finished.connect(self.clear_threads)
@ -806,16 +787,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if not ok or clone_name == "": if not ok or clone_name == "":
return return
self.thread = CloneVMThread(vm, self.qubes_app, clone_name) thread = common_threads.CloneVMThread(vm, clone_name)
self.thread.start() thread.finished.connect(self.clear_threads)
return self.threads_list.append(thread)
thread.start()
progress = QtGui.QProgressDialog(
self.tr("Cloning Qube <b>{0}</b> to <b>{1}</b>...").format(
vm.name, clone_name), "", 0, 0)
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
# noinspection PyArgumentList # noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_resumevm_triggered') @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"]: if vm.get_power_state() in ["Paused", "Suspended"]:
try: try:
vm.unpause() vm.unpause()
self.vms_in_table[vm.qid].update()
self.table_selection_changed()
except exc.QubesException as ex: except exc.QubesException as ex:
QtGui.QMessageBox.warning( QtGui.QMessageBox.warning(
None, self.tr("Error unpausing Qube!"), None, self.tr("Error unpausing Qube!"),
@ -841,7 +814,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
thread = StartVMThread(vm) thread = StartVMThread(vm)
self.threads_list.append(thread) self.threads_list.append(thread)
self.connect(thread, QtCore.SIGNAL("show_error(QString, QString)"), self.show_error)
thread.finished.connect(self.clear_threads) thread.finished.connect(self.clear_threads)
thread.start() thread.start()
@ -1016,7 +988,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
thread = UpdateVMThread(vm) thread = UpdateVMThread(vm)
self.threads_list.append(thread) self.threads_list.append(thread)
self.connect(thread, QtCore.SIGNAL("show_error(QString, QString)"), self.show_error)
thread.finished.connect(self.clear_threads) thread.finished.connect(self.clear_threads)
thread.start() thread.start()
@ -1035,11 +1006,9 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
thread = RunCommandThread(vm, command_to_run) thread = RunCommandThread(vm, command_to_run)
self.threads_list.append(thread) self.threads_list.append(thread)
self.connect(thread, QtCore.SIGNAL("show_error(QString, QString)"), self.show_error)
thread.finished.connect(self.clear_threads) thread.finished.connect(self.clear_threads)
thread.start() thread.start()
# noinspection PyArgumentList # noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_set_keyboard_layout_triggered') @QtCore.pyqtSlot(name='on_action_set_keyboard_layout_triggered')
def action_set_keyboard_layout_triggered(self): def action_set_keyboard_layout_triggered(self):
@ -1079,7 +1048,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
# noinspection PyArgumentList # noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_backup_triggered') @QtCore.pyqtSlot(name='on_action_backup_triggered')
def action_backup_triggered(self): 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() backup_window.show()
# noinspection PyArgumentList # noinspection PyArgumentList

View File

@ -38,7 +38,6 @@ from qubes import backup
from . import ui_restoredlg # pylint: disable=no-name-in-module from . import ui_restoredlg # pylint: disable=no-name-in-module
from . import multiselectwidget from . import multiselectwidget
from . import backup_utils from . import backup_utils
from . import thread_monitor
from multiprocessing import Queue, Event from multiprocessing import Queue, Event
from multiprocessing.queues import Empty from multiprocessing.queues import Empty
@ -46,8 +45,37 @@ from qubesadmin import Qubes, exc
from qubesadmin.backup import restore 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 = '<b><font color="red">{0}</font></b>'.format(
self.tr("Finished with errors!"))
else:
self.msg = '<font color="green">{0}</font>'.format(
self.tr("Finished successfully!"))
class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
def __init__(self, qt_app, qubes_app, parent=None): def __init__(self, qt_app, qubes_app, parent=None):
super(RestoreVMsWindow, self).__init__(parent) super(RestoreVMsWindow, self).__init__(parent)
@ -64,9 +92,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
logger.addHandler(handler) logger.addHandler(handler)
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
self.canceled = False
self.error_detected = Event()
self.thread_monitor = None
self.backup_restore = None self.backup_restore = None
self.target_appvm = None self.target_appvm = None
@ -148,37 +173,8 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
def append_output(self, text): def append_output(self, text):
self.commit_text_edit.append(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('<b><font color="red">{0}</font></b>'.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('<b><font color="red">{0}</font></b>'.format(
self.tr("Finished with errors!")))
else:
self.append_output('<font color="green">{0}</font>'.format(
self.tr("Finished successfully!")))
t_monitor.set_finished()
def current_page_changed(self, page_id): # pylint: disable=unused-argument def current_page_changed(self, page_id): # pylint: disable=unused-argument
self.old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
if self.currentPage() is self.select_vms_page: if self.currentPage() is self.select_vms_page:
self.__fill_vms_list__() self.__fill_vms_list__()
@ -210,14 +206,43 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
and str(self.dir_line_edit.text()) and str(self.dir_line_edit.text())
.count("media/") > 0) .count("media/") > 0)
self.thread_monitor = thread_monitor.ThreadMonitor() self.thread = RestoreThread(self.backup_restore, self.vms_to_restore)
thread = threading.Thread(target=self.__do_restore__, self.thread.finished.connect(self.thread_finished)
args=(self.thread_monitor,))
thread.daemon = True # Start log timer
thread.start() timer = QtCore.QTimer(self)
while not self.thread_monitor.is_finished(): timer.timeout.connect(self.update_log)
self.qt_app.processEvents() timer.start(1000)
time.sleep(0.1)
self.thread.start()
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(
'<b><font color="black">{0}</font></b>'.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: try:
log_record = self.feedback_queue.get_nowait() log_record = self.feedback_queue.get_nowait()
while log_record: while log_record:
@ -232,29 +257,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
except Empty: except Empty:
pass pass
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)
if self.showFileDialog.isChecked():
self.append_output(
'<b><font color="black">{0}</font></b>'.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.button(self.FinishButton).setEnabled(True)
self.button(self.CancelButton).setEnabled(False)
self.showFileDialog.setEnabled(False)
signal.signal(signal.SIGCHLD, old_sigchld_handler)
def all_vms_good(self): def all_vms_good(self):
for vm_info in self.vms_to_restore.values(): for vm_info in self.vms_to_restore.values():
@ -265,11 +267,13 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
return True return True
def reject(self): 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.backup_restore.canceled = True
self.append_output('<font color="red">{0}</font>'.format( self.append_output('<font color="red">{0}</font>'.format(
self.tr("Aborting the operation..."))) self.tr("Aborting the operation...")))
self.button(self.CancelButton).setDisabled(True) self.button(self.CancelButton).setDisabled(True)
self.thread.terminate()
else: else:
self.done(0) self.done(0)

View File

@ -39,7 +39,7 @@ import qubesadmin.exc
from . import utils from . import utils
from . import multiselectwidget from . import multiselectwidget
from . import thread_monitor from . import common_threads
from . import device_list from . import device_list
from .appmenu_select import AppmenuSelectManager 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 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.<br> ").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 # pylint: disable=too-many-instance-attributes
class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog): class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
tabs_indices = collections.OrderedDict(( tabs_indices = collections.OrderedDict((
@ -65,6 +136,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.vm = vm self.vm = vm
self.qapp = qapp self.qapp = qapp
self.threads_list = []
try: try:
self.source_vm = self.vm.template self.source_vm = self.vm.template
except AttributeError: except AttributeError:
@ -150,6 +222,18 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.refresh_apps_button.clicked.connect( self.refresh_apps_button.clicked.connect(
self.refresh_apps_button_pressed) 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 def keyPressEvent(self, event): # pylint: disable=invalid-name
if event.key() == QtCore.Qt.Key_Enter \ if event.key() == QtCore.Qt.Key_Enter \
or event.key() == QtCore.Qt.Key_Return: or event.key() == QtCore.Qt.Key_Return:
@ -164,31 +248,14 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
pass pass
def save_changes(self): def save_changes(self):
t_monitor = thread_monitor.ThreadMonitor() error = self.__save_changes__()
thread = threading.Thread(target=self.__save_changes__,
args=(t_monitor,))
thread.daemon = True
thread.start()
progress = QtGui.QProgressDialog( if error:
self.tr("Applying settings to <b>{0}</b>...").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:
QtGui.QMessageBox.warning( QtGui.QMessageBox.warning(
self, self,
self.tr("Error while changing settings for {0}!" self.tr("Error while changing settings for {0}!"
).format(self.vm.name), ).format(self.vm.name),
self.tr("ERROR: {0}").format(t_monitor.error_msg)) self.tr("ERROR: {0}").format('\n'.join(ret)))
def apply(self): def apply(self):
self.save_changes() self.save_changes()
@ -197,9 +264,9 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.save_changes() self.save_changes()
self.done(0) self.done(0)
def __save_changes__(self, t_monitor): def __save_changes__(self):
ret = [] ret = []
try: try:
ret_tmp = self.__apply_basic_tab__() ret_tmp = self.__apply_basic_tab__()
if ret_tmp: if ret_tmp:
@ -237,12 +304,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
except Exception as ex: # pylint: disable=broad-except except Exception as ex: # pylint: disable=broad-except
ret += [self.tr("Applications tab:"), repr(ex)] ret += [self.tr("Applications tab:"), repr(ex)]
if ret:
t_monitor.set_error_msg('\n'.join(ret))
utils.debug('\n'.join(ret)) utils.debug('\n'.join(ret))
return ret
t_monitor.set_finished()
def check_network_availability(self): def check_network_availability(self):
netvm = self.vm.netvm netvm = self.vm.netvm
@ -464,61 +527,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
"allowed value.")) "allowed value."))
self.init_mem.setValue(self.max_mem_size.value() / 10) 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.<br> ").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): def rename_vm(self):
dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm) 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)')) self.tr('New name: (WARNING: all other changes will be discarded)'))
if ok: if ok:
if self._run_in_thread(self._rename_vm, new_vm_name, dependencies): thread = RenameVMThread(self.vm, new_vm_name, dependencies)
self.done(0) self.threads_list.append(thread)
thread.finished.connect(self.clear_threads)
def _remove_vm(self, t_monitor): thread.start()
try: thread.wait()
del self.vm.app.domains[self.vm.name] #self.done(0)
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 remove_vm(self): def remove_vm(self):
@ -584,7 +585,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
'qube\'s name below.')) 'qube\'s name below.'))
if ok and answer == self.vm.name: 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) self.done(0)
elif ok: elif ok:
@ -612,11 +614,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr('Name for the cloned qube:')) self.tr('Name for the cloned qube:'))
if ok: if ok:
self._run_in_thread(self._clone_vm, cloned_vm_name) thread = common_threads.CloneVMThread(self.vm, cloned_vm_name)
QtGui.QMessageBox.warning( thread.finished.connect(self.clear_threads)
self, self.threads_list.append(thread)
self.tr("Success"), thread.start()
self.tr("The qube was cloned successfully."))
######### advanced tab ######### advanced tab
@ -944,43 +945,19 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
######## applications tab ######## 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): def refresh_apps_button_pressed(self):
self.refresh_apps_button.setEnabled(False) self.refresh_apps_button.setEnabled(False)
self.refresh_apps_button.setText(self.tr('Refresh in progress...')) self.refresh_apps_button.setText(self.tr('Refresh in progress...'))
t_monitor = thread_monitor.ThreadMonitor() thread = RefreshAppsVMThread(self.vm)
thread = threading.Thread( thread.finished.connect(self.clear_threads)
target=self.refresh_apps_in_vm, thread.finished.connect(self.refresh_finished)
args=(t_monitor,)) self.threads_list.append(thread)
thread.daemon = True
thread.start() thread.start()
while not t_monitor.is_finished(): def refresh_finished(self):
self.qapp.processEvents()
time.sleep(0.1)
self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list) self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list)
self.refresh_apps_button.setEnabled(True) self.refresh_apps_button.setEnabled(True)
self.refresh_apps_button.setText(self.tr('Refresh Applications')) self.refresh_apps_button.setText(self.tr('Refresh Applications'))

View File

@ -1,42 +0,0 @@
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2011 Marek Marczykowski <marmarek@mimuw.edu.pl>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
#
#
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()