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 signal
import quamash
from qubesadmin import Qubes, exc
from qubesadmin import utils as admin_utils
from qubesadmin import events
from qubes.storage.file import get_disk_usage
from PyQt4 import QtCore # pylint: disable=import-error
@ -35,18 +37,38 @@ from . import multiselectwidget
from . import backup_utils
from . import utils
import grp
import pwd
import sys
import os
from . import thread_monitor
import threading
import asyncio
from contextlib import suppress
import time
class BackupThread(QtCore.QThread):
def __init__(self, vm):
QtCore.QThread.__init__(self)
self.vm = vm
self.msg = None
def run(self):
msg = []
try:
if not self.vm.is_running():
self.vm.start()
self.vm.app.qubesd_call(
'dom0', 'admin.backup.Execute',
backup_utils.get_profile_name(True))
except Exception as ex: # pylint: disable=broad-except
msg.append(str(ex))
if msg:
self.msg = '\n'.join(msg)
class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
def __init__(self, qt_app, qubes_app, parent=None):
def __init__(self, qt_app, qubes_app, dispatcher, parent=None):
super(BackupVMsWindow, self).__init__(parent)
self.qt_app = qt_app
@ -54,8 +76,6 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
self.backup_settings = QtCore.QSettings()
self.selected_vms = []
self.canceled = False
self.thread_monitor = None
self.setupUi(self)
@ -112,6 +132,17 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
selected = self.load_settings()
self.__fill_vms_list__(selected)
# Connect backup events for progress_bar
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(100)
self.dispatcher = dispatcher
dispatcher.add_handler('backup-progress', self.on_backup_progress)
def on_backup_progress(self, __submitter, _event, **kwargs):
print(kwargs['progress'])
self.progress_bar.setValue(int(float(kwargs['progress'])))
def load_settings(self):
"""
Helper function that tries to load existing backup profile
@ -260,24 +291,6 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
return True
def __do_backup__(self, t_monitor):
msg = []
try:
vm = self.qubes_app.domains[
self.appvm_combobox.currentText()]
if not vm.is_running():
vm.start()
self.qubes_app.qubesd_call(
'dom0', 'admin.backup.Execute',
backup_utils.get_profile_name(True))
except Exception as ex: # pylint: disable=broad-except
msg.append(str(ex))
if msg:
t_monitor.set_error_msg('\n'.join(msg))
t_monitor.set_finished()
@staticmethod
def cleanup_temporary_files():
@ -310,33 +323,27 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
self.showFileDialog.setChecked(self.showFileDialog.isEnabled()
and str(self.dir_line_edit.text())
.count("media/") > 0)
self.thread_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(
target=self.__do_backup__,
args=(self.thread_monitor,))
thread.daemon = True
thread.start()
while not self.thread_monitor.is_finished():
self.qt_app.processEvents()
time.sleep(0.1)
vm = self.qubes_app.domains[
self.appvm_combobox.currentText()]
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.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_monitor.error_msg))
self.thread.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,29 +351,26 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
" Please unmount your backup volume and cancel "
"the file selection dialog."))
backup_utils.select_path_button_clicked(self, False, True)
self.button(self.CancelButton).setEnabled(False)
self.button(self.FinishButton).setEnabled(True)
self.showFileDialog.setEnabled(False)
self.cleanup_temporary_files()
# turn off only when backup was successful
if self.thread_monitor.success and \
self.turn_off_checkbox.isChecked():
if self.turn_off_checkbox.isChecked():
os.system('systemctl poweroff')
signal.signal(signal.SIGCHLD, old_sigchld_handler)
def reject(self):
if self.currentPage() is self.commit_page:
self.canceled = True
self.thread.terminate()
self.qubes_app.qubesd_call(
'dom0', 'admin.backup.Cancel',
backup_utils.get_profile_name(True))
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(0)
self.button(self.CancelButton).setDisabled(True)
self.cleanup_temporary_files()
else:
QtGui.QMessageBox.warning(
self, self.tr("Backup aborted!"),
self.tr("ERROR: {}").format("Aborted!"))
self.cleanup_temporary_files()
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/>"
% (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__":

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 .ui_newappvmdlg import Ui_NewVMDlg # pylint: disable=import-error
from .thread_monitor import ThreadMonitor
class CreateVMThread(QtCore.QThread):
def __init__(self, app, vmclass, name, label, template, properties):
QtCore.QThread.__init__(self)
self.app = app
self.vmclass = vmclass
self.name = name
self.label = label
self.template = template
self.properties = properties
self.error = None
def run(self):
try:
if self.vmclass == 'StandaloneVM' and self.template is not None:
if self.template is qubesadmin.DEFAULT:
src_vm = self.app.default_template
else:
src_vm = self.template
vm = self.app.clone_vm(src_vm, self.name, self.vmclass)
vm.label = self.label
for k, v in self.properties.items():
setattr(vm, k, v)
else:
vm = self.app.add_new_vm(self.vmclass,
name=self.name, label=self.label, template=self.template)
for k, v in self.properties.items():
setattr(vm, k, v)
except qubesadmin.exc.QubesException as qex:
self.error = str(qex)
except Exception as ex: # pylint: disable=broad-except
self.error = repr(ex)
class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
@ -125,67 +157,36 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
properties['virt_mode'] = 'hvm'
properties['kernel'] = None
thread_monitor = ThreadMonitor()
thread = threading.Thread(target=self.do_create_vm,
args=(self.app, vmclass, name, label, template, properties,
thread_monitor))
thread.daemon = True
thread.start()
self.thread = CreateVMThread(self.app, vmclass, name, label,
template, properties)
self.thread.finished.connect(self.create_finished)
self.thread.start()
progress = QtGui.QProgressDialog(
self.progress = QtGui.QProgressDialog(
self.tr("Creating new qube <b>{}</b>...").format(name), "", 0, 0)
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
self.progress.setCancelButton(None)
self.progress.setModal(True)
self.progress.show()
while not thread_monitor.is_finished():
self.qtapp.processEvents()
time.sleep(0.1)
def create_finished(self):
self.progress.hide()
progress.hide()
if not thread_monitor.success:
if self.thread.error:
QtGui.QMessageBox.warning(None,
self.tr("Error creating the qube!"),
self.tr("ERROR: {}").format(thread_monitor.error_msg))
self.tr("ERROR: {}").format(thread.error))
self.done(0)
if thread_monitor.success:
if not self.thread.error:
if self.launch_settings.isChecked():
subprocess.check_call(['qubes-vm-settings', name])
subprocess.check_call(['qubes-vm-settings', str(self.name)])
if self.install_system.isChecked():
subprocess.check_call(
['qubes-vm-boot-from-device', name])
['qubes-vm-boot-from-device', srt(self.name)])
@staticmethod
def do_create_vm(app, vmclass, name, label, template, properties,
thread_monitor):
try:
if vmclass == 'StandaloneVM' and template is not None:
if template is qubesadmin.DEFAULT:
src_vm = app.default_template
else:
src_vm = template
vm = app.clone_vm(src_vm, name, vmclass)
vm.label = label
for k, v in properties.items():
setattr(vm, k, v)
else:
vm = app.add_new_vm(vmclass,
name=name, label=label, template=template)
for k, v in properties.items():
setattr(vm, k, v)
except qubesadmin.exc.QubesException as qex:
thread_monitor.set_error_msg(str(qex))
except Exception as ex: # pylint: disable=broad-except
thread_monitor.set_error_msg(repr(ex))
thread_monitor.set_finished()
def type_change(self):
# AppVM
if self.vm_type.currentIndex() == 0:
self.template_vm.setEnabled(True)

View File

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

View File

@ -38,7 +38,6 @@ from qubes import backup
from . import ui_restoredlg # pylint: disable=no-name-in-module
from . import multiselectwidget
from . import backup_utils
from . import thread_monitor
from multiprocessing import Queue, Event
from multiprocessing.queues import Empty
@ -46,8 +45,37 @@ from qubesadmin import Qubes, exc
from qubesadmin.backup import restore
class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
class RestoreThread(QtCore.QThread):
def __init__(self, backup_restore, vms_to_restore):
QtCore.QThread.__init__(self)
self.backup_restore = backup_restore
self.vms_to_restore = vms_to_restore
self.error = None
self.msg = None
def run(self):
err_msg = []
try:
self.backup_restore.restore_do(self.vms_to_restore)
except backup.BackupCanceledError as ex:
self.canceled = True
err_msg.append(str(ex))
except Exception as ex: # pylint: disable=broad-except
err_msg.append(str(ex))
err_msg.append(
self.tr("Partially restored files left in /var/tmp/restore_*, "
"investigate them and/or clean them up"))
if err_msg:
self.error = '\n'.join(err_msg)
self.msg = '<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):
super(RestoreVMsWindow, self).__init__(parent)
@ -64,9 +92,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
logger.addHandler(handler)
logger.setLevel(logging.INFO)
self.canceled = False
self.error_detected = Event()
self.thread_monitor = None
self.backup_restore = None
self.target_appvm = None
@ -148,37 +173,8 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
def append_output(self, text):
self.commit_text_edit.append(text)
def __do_restore__(self, t_monitor):
err_msg = []
try:
self.backup_restore.restore_do(self.vms_to_restore)
except backup.BackupCanceledError as ex:
self.canceled = True
err_msg.append(str(ex))
except Exception as ex: # pylint: disable=broad-except
err_msg.append(str(ex))
err_msg.append(
self.tr("Partially restored files left in /var/tmp/restore_*, "
"investigate them and/or clean them up"))
if self.canceled:
self.append_output('<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
old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
self.old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
if self.currentPage() is self.select_vms_page:
self.__fill_vms_list__()
@ -210,14 +206,43 @@ 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)
self.thread = RestoreThread(self.backup_restore, self.vms_to_restore)
self.thread.finished.connect(self.thread_finished)
# Start log timer
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_log)
timer.start(1000)
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:
log_record = self.feedback_queue.get_nowait()
while log_record:
@ -232,29 +257,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
except Empty:
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):
for vm_info in self.vms_to_restore.values():
@ -265,11 +267,13 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
return True
def reject(self):
if self.currentPage() is self.commit_page:
if self.currentPage() is self.commit_page and self.thread.isRunning():
self.backup_restore.canceled = True
self.append_output('<font color="red">{0}</font>'.format(
self.tr("Aborting the operation...")))
self.button(self.CancelButton).setDisabled(True)
self.thread.terminate()
else:
self.done(0)

View File

@ -39,7 +39,7 @@ import qubesadmin.exc
from . import utils
from . import multiselectwidget
from . import thread_monitor
from . import common_threads
from . import device_list
from .appmenu_select import AppmenuSelectManager
@ -49,6 +49,77 @@ from PyQt4 import QtCore, QtGui # pylint: disable=import-error
from . import ui_settingsdlg # pylint: disable=no-name-in-module
class RenameVMThread(QtCore.QThread):
def __init__(self, vm, new_vm_name, dependencies):
QtCore.QThread.__init__(self)
self.vm = vm
self.new_vm_name = new_vm_name
self.dependencies = dependencies
def run(self):
try:
new_vm = self.vm.app.clone_vm(self.vm, self.new_vm_name)
failed_props = []
for (holder, prop) in self.dependencies:
try:
if holder is None:
setattr(self.vm.app, prop, new_vm)
else:
setattr(holder, prop, new_vm)
except exc.QubesException as qex:
failed_props += [(holder, prop)]
if not failed_props:
del self.vm.app.domains[self.vm.name]
else:
list_text = utils.format_dependencies_list(failed_props)
QtGui.QMessageBox.warning(
self,
self.tr("Warning: rename partially unsuccessful"),
self.tr("Some properties could not be changed to the new "
"name. The system has now both {} and {} qubes. "
"To resolve this, please check and change the "
"following properties and remove the qube {} "
"manually.<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
class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
tabs_indices = collections.OrderedDict((
@ -65,6 +136,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.vm = vm
self.qapp = qapp
self.threads_list = []
try:
self.source_vm = self.vm.template
except AttributeError:
@ -150,6 +222,18 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.refresh_apps_button.clicked.connect(
self.refresh_apps_button_pressed)
def clear_threads(self):
for thread in self.threads_list:
if thread.isFinished():
if thread.error:
(title, msg) = thread.error
QtGui.QMessageBox.warning(
None,
self.tr(title),
self.tr("ERROR: {0}").format(msg))
self.threads_list.remove(thread)
def keyPressEvent(self, event): # pylint: disable=invalid-name
if event.key() == QtCore.Qt.Key_Enter \
or event.key() == QtCore.Qt.Key_Return:
@ -164,31 +248,14 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
pass
def save_changes(self):
t_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(target=self.__save_changes__,
args=(t_monitor,))
thread.daemon = True
thread.start()
error = self.__save_changes__()
progress = QtGui.QProgressDialog(
self.tr("Applying settings to <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:
if error:
QtGui.QMessageBox.warning(
self,
self.tr("Error while changing settings for {0}!"
).format(self.vm.name),
self.tr("ERROR: {0}").format(t_monitor.error_msg))
self.tr("ERROR: {0}").format('\n'.join(ret)))
def apply(self):
self.save_changes()
@ -197,9 +264,9 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.save_changes()
self.done(0)
def __save_changes__(self, t_monitor):
def __save_changes__(self):
ret = []
try:
ret_tmp = self.__apply_basic_tab__()
if ret_tmp:
@ -237,12 +304,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
except Exception as ex: # pylint: disable=broad-except
ret += [self.tr("Applications tab:"), repr(ex)]
if ret:
t_monitor.set_error_msg('\n'.join(ret))
utils.debug('\n'.join(ret))
t_monitor.set_finished()
return ret
def check_network_availability(self):
netvm = self.vm.netvm
@ -464,61 +527,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
"allowed value."))
self.init_mem.setValue(self.max_mem_size.value() / 10)
def _run_in_thread(self, func, *args):
t_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(target=func, args=(t_monitor, *args,))
thread.daemon = True
thread.start()
while not t_monitor.is_finished():
self.qapp.processEvents()
time.sleep(0.1)
if not t_monitor.success:
QtGui.QMessageBox.warning(self,
self.tr("Error!"),
self.tr("ERROR: {}").format(
t_monitor.error_msg))
return False
return True
def _rename_vm(self, t_monitor, name, dependencies):
try:
new_vm = self.vm.app.clone_vm(self.vm, name)
failed_props = []
for (holder, prop) in dependencies:
try:
if holder is None:
setattr(self.vm.app, prop, new_vm)
else:
setattr(holder, prop, new_vm)
except qubesadmin.exc.QubesException as qex:
failed_props += [(holder, prop)]
if not failed_props:
del self.vm.app.domains[self.vm.name]
else:
list_text = utils.format_dependencies_list(failed_props)
QtGui.QMessageBox.warning(
self,
self.tr("Warning: rename partially unsuccessful"),
self.tr("Some properties could not be changed to the new "
"name. The system has now both {} and {} qubes. "
"To resolve this, please check and change the "
"following properties and remove the qube {} "
"manually.<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):
dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm)
@ -544,19 +552,12 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr('New name: (WARNING: all other changes will be discarded)'))
if ok:
if self._run_in_thread(self._rename_vm, new_vm_name, dependencies):
self.done(0)
def _remove_vm(self, t_monitor):
try:
del self.vm.app.domains[self.vm.name]
except qubesadmin.exc.QubesException as qex:
t_monitor.set_error_msg(str(qex))
except Exception as ex: # pylint: disable=broad-except
t_monitor.set_error_msg(repr(ex))
t_monitor.set_finished()
thread = RenameVMThread(self.vm, new_vm_name, dependencies)
self.threads_list.append(thread)
thread.finished.connect(self.clear_threads)
thread.start()
thread.wait()
#self.done(0)
def remove_vm(self):
@ -584,7 +585,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
'qube\'s name below.'))
if ok and answer == self.vm.name:
self._run_in_thread(self._remove_vm)
thread = common_threads.RemoveVMThread(self.vm)
thread.start()
self.done(0)
elif ok:
@ -612,11 +614,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr('Name for the cloned qube:'))
if ok:
self._run_in_thread(self._clone_vm, cloned_vm_name)
QtGui.QMessageBox.warning(
self,
self.tr("Success"),
self.tr("The qube was cloned successfully."))
thread = common_threads.CloneVMThread(self.vm, cloned_vm_name)
thread.finished.connect(self.clear_threads)
self.threads_list.append(thread)
thread.start()
######### advanced tab
@ -944,43 +945,19 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
######## applications tab
def refresh_apps_in_vm(self, t_monitor):
try:
target_vm = self.vm.template
except AttributeError:
target_vm = self.vm
if not target_vm.is_running():
not_running = True
target_vm.start()
else:
not_running = False
subprocess.check_call(['qvm-sync-appmenus', target_vm.name])
if not_running:
target_vm.shutdown()
t_monitor.set_finished()
def refresh_apps_button_pressed(self):
self.refresh_apps_button.setEnabled(False)
self.refresh_apps_button.setText(self.tr('Refresh in progress...'))
t_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(
target=self.refresh_apps_in_vm,
args=(t_monitor,))
thread.daemon = True
thread = RefreshAppsVMThread(self.vm)
thread.finished.connect(self.clear_threads)
thread.finished.connect(self.refresh_finished)
self.threads_list.append(thread)
thread.start()
while not t_monitor.is_finished():
self.qapp.processEvents()
time.sleep(0.1)
def refresh_finished(self):
self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list)
self.refresh_apps_button.setEnabled(True)
self.refresh_apps_button.setText(self.tr('Refresh Applications'))

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()