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:
parent
35cdf5e1e5
commit
29ca4eb3a8
@ -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__":
|
||||||
|
51
qubesmanager/common_threads.py
Normal file
51
qubesmanager/common_threads.py
Normal 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))
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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'))
|
||||||
|
|
||||||
|
@ -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()
|
|
Loading…
Reference in New Issue
Block a user