Merge remote-tracking branch 'origin/pr/132'

* origin/pr/132: (48 commits)
  Fixed unreachable code thanks to travis
  Clear searchbox pressing esc without need of selecting it
  Fix errors when some domain fails to start
  wait thread to finish when aborting
  Don't terminate backup thread when aborting
  Fix iterating and removing over list
  Use 'qube' instead 'Qube'
  Qube -> qube
  Catch possible KeyError when starting dispVM
  Fix authorship and python version
  Create backup_window on the stack
  Removed sigchld_handler old code
  Fix pylint
  Do not terminate the thread
  Fix error/success message on dialog
  Fix opening settings/boot dialog after VM creation
  Add pylint disable too-few-methods
  Fix travis errors
  Removed unused import
  Workaround for backup dialog modeless behaviour
  ...
This commit is contained in:
Marek Marczykowski-Górecki 2018-11-28 01:13:48 +01:00
commit 334fefe559
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
10 changed files with 584 additions and 640 deletions

View File

@ -17,6 +17,7 @@ SOURCES = \
qubesmanager/block.py \ qubesmanager/block.py \
qubesmanager/clipboard.py \ qubesmanager/clipboard.py \
qubesmanager/create_new_vm.py \ qubesmanager/create_new_vm.py \
qubesmanager/common_threads.py \
qubesmanager/firewall.py \ qubesmanager/firewall.py \
qubesmanager/global_settings.py \ qubesmanager/global_settings.py \
qubesmanager/log_dialog.py \ qubesmanager/log_dialog.py \
@ -27,7 +28,6 @@ SOURCES = \
qubesmanager/restore.py \ qubesmanager/restore.py \
qubesmanager/settings.py \ qubesmanager/settings.py \
qubesmanager/table_widgets.py \ qubesmanager/table_widgets.py \
qubesmanager/thread_monitor.py \
qubesmanager/ui_about.py \ qubesmanager/ui_about.py \
qubesmanager/ui_backupdlg.py \ qubesmanager/ui_backupdlg.py \
qubesmanager/ui_globalsettingsdlg.py \ qubesmanager/ui_globalsettingsdlg.py \

View File

@ -20,7 +20,6 @@
# with this program; if not, see <http://www.gnu.org/licenses/>. # with this program; if not, see <http://www.gnu.org/licenses/>.
# #
# #
from PyQt4.QtCore import SIGNAL, SLOT # pylint: disable=import-error
from PyQt4.QtGui import QDialog, QIcon # pylint: disable=import-error from PyQt4.QtGui import QDialog, QIcon # pylint: disable=import-error
from qubesmanager.releasenotes import ReleaseNotesDialog from qubesmanager.releasenotes import ReleaseNotesDialog
from qubesmanager.informationnotes import InformationNotesDialog from qubesmanager.informationnotes import InformationNotesDialog
@ -28,6 +27,7 @@ from qubesmanager.informationnotes import InformationNotesDialog
from . import ui_about # pylint: disable=no-name-in-module from . import ui_about # pylint: disable=no-name-in-module
# pylint: disable=too-few-public-methods
class AboutDialog(ui_about.Ui_AboutDialog, QDialog): class AboutDialog(ui_about.Ui_AboutDialog, QDialog):
def __init__(self): def __init__(self):
super(AboutDialog, self).__init__() super(AboutDialog, self).__init__()
@ -38,18 +38,14 @@ class AboutDialog(ui_about.Ui_AboutDialog, QDialog):
with open('/etc/qubes-release', 'r') as release_file: with open('/etc/qubes-release', 'r') as release_file:
self.release.setText(release_file.read()) self.release.setText(release_file.read())
self.connect(self.ok, SIGNAL("clicked()"), SLOT("accept()")) self.ok.clicked.connect(self.accept)
self.connect(self.releaseNotes, SIGNAL("clicked()"), self.releaseNotes.clicked.connect(on_release_notes_clicked)
self.on_release_notes_clicked) self.informationNotes.clicked.connect(on_information_notes_clicked)
self.connect(self.informationNotes, SIGNAL("clicked()"),
self.on_information_notes_clicked)
def on_release_notes_clicked(self): def on_release_notes_clicked():
release_notes_dialog = ReleaseNotesDialog() release_notes_dialog = ReleaseNotesDialog()
release_notes_dialog.exec_() release_notes_dialog.exec_()
self.accept()
def on_information_notes_clicked(self): def on_information_notes_clicked():
information_notes_dialog = InformationNotesDialog() information_notes_dialog = InformationNotesDialog()
information_notes_dialog.exec_() information_notes_dialog.exec_()
self.accept()

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
# pylint: disable=too-few-public-methods
class BackupThread(QtCore.QThread):
def __init__(self, vm):
QtCore.QThread.__init__(self)
self.vm = vm
self.msg = None
def run(self):
msg = []
try:
if not self.vm.is_running():
self.vm.start()
self.vm.app.qubesd_call(
'dom0', 'admin.backup.Execute',
backup_utils.get_profile_name(True))
except Exception as ex: # pylint: disable=broad-except
msg.append(str(ex))
if msg:
self.msg = '\n'.join(msg)
class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard): 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,7 @@ 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 = None
self.thread_monitor = None
self.setupUi(self) self.setupUi(self)
@ -112,6 +133,16 @@ 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):
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)
self.thread = BackupThread(vm)
self.thread.finished.connect(self.backup_finished)
self.thread.start()
signal.signal(signal.SIGCHLD, old_sigchld_handler)
def backup_finished(self):
if self.thread.msg:
self.progress_status.setText(self.tr("Backup error."))
QtGui.QMessageBox.warning(
self, self.tr("Backup error!"),
self.tr("ERROR: {}").format(
self.thread.msg))
else:
self.progress_bar.setValue(100)
self.progress_status.setText(self.tr("Backup finished."))
if not self.thread_monitor.success:
if self.canceled:
self.progress_status.setText(
self.tr(
"Backup aborted. "
"Temporary file may be left at backup location."))
else:
self.progress_status.setText(self.tr("Backup error."))
QtGui.QMessageBox.warning(
self, self.tr("Backup error!"),
self.tr("ERROR: {}").format(
self.thread_monitor.error_msg))
else:
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(100)
self.progress_status.setText(self.tr("Backup finished."))
if self.showFileDialog.isChecked(): 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,31 +351,28 @@ 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.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) self.thread.wait()
self.progress_bar.setValue(0) QtGui.QMessageBox.warning(
self.button(self.CancelButton).setDisabled(True) self, self.tr("Backup aborted!"),
self.cleanup_temporary_files() self.tr("ERROR: {}").format("Aborted!"))
else:
self.cleanup_temporary_files() self.cleanup_temporary_files()
self.done(0) self.done(0)
def has_selected_vms(self): def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0 return self.select_vms_widget.selected_list.count() > 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,54 @@
#!/usr/bin/python3
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2018 Donoban <donoban@riseup.net>
#
# 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
# pylint: disable=too-few-public-methods
class RemoveVMThread(QtCore.QThread):
def __init__(self, vm):
QtCore.QThread.__init__(self)
self.vm = vm
self.msg = None
def run(self):
try:
del self.vm.app.domains[self.vm.name]
except (exc.QubesException, KeyError) as ex:
self.msg = ("Error removing qube!", str(ex))
# pylint: disable=too-few-public-methods
class CloneVMThread(QtCore.QThread):
def __init__(self, src_vm, dst_name):
QtCore.QThread.__init__(self)
self.src_vm = src_vm
self.dst_name = dst_name
self.msg = None
def run(self):
try:
self.src_vm.app.clone_vm(self.src_vm, self.dst_name)
self.msg = ("Sucess", "The qube was cloned sucessfully.")
except exc.QubesException as ex:
self.msg = ("Error while cloning qube!", str(ex))

View File

@ -22,8 +22,6 @@
# #
import sys import sys
import threading
import time
import subprocess import subprocess
from PyQt4 import QtCore, QtGui # pylint: disable=import-error from PyQt4 import QtCore, QtGui # pylint: disable=import-error
@ -35,7 +33,40 @@ 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
# pylint: disable=too-few-public-methods
class CreateVMThread(QtCore.QThread):
def __init__(self, app, vmclass, name, label, template, properties):
QtCore.QThread.__init__(self)
self.app = app
self.vmclass = vmclass
self.name = name
self.label = label
self.template = template
self.properties = properties
self.msg = None
def run(self):
try:
if self.vmclass == 'StandaloneVM' and self.template is not None:
if self.template is qubesadmin.DEFAULT:
src_vm = self.app.default_template
else:
src_vm = self.template
vm = self.app.clone_vm(src_vm, self.name, self.vmclass)
vm.label = self.label
for k, v in self.properties.items():
setattr(vm, k, v)
else:
vm = self.app.add_new_vm(self.vmclass,
name=self.name, label=self.label, template=self.template)
for k, v in self.properties.items():
setattr(vm, k, v)
except qubesadmin.exc.QubesException as qex:
self.msg = str(qex)
except Exception as ex: # pylint: disable=broad-except
self.msg = repr(ex)
class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg): class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
@ -46,6 +77,9 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
self.qtapp = qtapp self.qtapp = qtapp
self.app = app self.app = app
self.thread = None
self.progress = None
# Theoretically we should be locking for writing here and unlock # Theoretically we should be locking for writing here and unlock
# only after the VM creation finished. But the code would be # only after the VM creation finished. But the code would be
# more messy... # more messy...
@ -125,67 +159,37 @@ 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.msg:
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(self.thread.msg))
self.done(0) self.done(0)
if thread_monitor.success: if not self.thread.msg:
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.text())])
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', str(self.name.text())])
@staticmethod
def do_create_vm(app, vmclass, name, label, template, properties,
thread_monitor):
try:
if vmclass == 'StandaloneVM' and template is not None:
if template is qubesadmin.DEFAULT:
src_vm = app.default_template
else:
src_vm = template
vm = app.clone_vm(src_vm, name, vmclass)
vm.label = label
for k, v in properties.items():
setattr(vm, k, v)
else:
vm = app.add_new_vm(vmclass,
name=name, label=label, template=template)
for k, v in properties.items():
setattr(vm, k, v)
except qubesadmin.exc.QubesException as qex:
thread_monitor.set_error_msg(str(qex))
except Exception as ex: # pylint: disable=broad-except
thread_monitor.set_error_msg(repr(ex))
thread_monitor.set_finished()
def type_change(self): 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,13 +25,12 @@ 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
import threading from contextlib import suppress
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
@ -44,14 +43,15 @@ from PyQt4 import QtCore # pylint: disable=import-error
from qubesmanager.about import AboutDialog from qubesmanager.about import AboutDialog
from . import ui_qubemanager # pylint: disable=no-name-in-module from . import ui_qubemanager # pylint: disable=no-name-in-module
from . import thread_monitor
from . import table_widgets from . import table_widgets
from . import settings from . import settings
from . import global_settings from . import global_settings
from . import restore from . import restore
from . import backup from . import backup
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):
@ -209,7 +209,7 @@ class VmShutdownMonitor(QtCore.QObject):
self.tr( self.tr(
"The Qube <b>'{0}'</b> hasn't shutdown within the last " "The Qube <b>'{0}'</b> hasn't shutdown within the last "
"{1} seconds, do you want to kill it?<br>").format( "{1} seconds, do you want to kill it?<br>").format(
vm.name, self.shutdown_time / 1000), vm.name, self.shutdown_time / 1000),
self.tr("Kill it!"), self.tr("Kill it!"),
self.tr("Wait another {0} seconds...").format( self.tr("Wait another {0} seconds...").format(
self.shutdown_time / 1000)) self.shutdown_time / 1000))
@ -238,6 +238,56 @@ class VmShutdownMonitor(QtCore.QObject):
self.restart_vm_if_needed() self.restart_vm_if_needed()
# pylint: disable=too-few-public-methods
class StartVMThread(QtCore.QThread):
def __init__(self, vm):
QtCore.QThread.__init__(self)
self.vm = vm
self.msg = None
def run(self):
try:
self.vm.start()
except exc.QubesException as ex:
self.msg = ("Error starting Qube!", str(ex))
# pylint: disable=too-few-public-methods
class UpdateVMThread(QtCore.QThread):
def __init__(self, vm):
QtCore.QThread.__init__(self)
self.vm = vm
self.msg = None
def run(self):
try:
if self.vm.qid == 0:
subprocess.check_call(
["/usr/bin/qubes-dom0-update", "--clean", "--gui"])
else:
if not self.vm.is_running():
self.vm.start()
self.vm.run_service("qubes.InstallUpdatesGUI",\
user="root", wait=False)
except (ChildProcessError, exc.QubesException) as ex:
self.msg = ("Error on qube update!", str(ex))
# pylint: disable=too-few-public-methods
class RunCommandThread(QtCore.QThread):
def __init__(self, vm, command_to_run):
QtCore.QThread.__init__(self)
self.vm = vm
self.command_to_run = command_to_run
self.msg = None
def run(self):
try:
self.vm.run(self.command_to_run)
except (ChildProcessError, exc.QubesException) as ex:
self.msg = ("Error while running command!", str(ex))
class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow): class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
row_height = 30 row_height = 30
@ -256,7 +306,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
"IP": 8, "IP": 8,
"Backups": 9, "Backups": 9,
"Last backup": 10, "Last backup": 10,
} }
def __init__(self, qt_app, qubes_app, dispatcher, parent=None): def __init__(self, qt_app, qubes_app, dispatcher, parent=None):
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -398,7 +448,11 @@ 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)
@ -410,12 +464,40 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
dispatcher.add_handler('property-load', dispatcher.add_handler('property-load',
self.on_domain_changed) self.on_domain_changed)
# It needs to store threads until they finish
self.threads_list = []
self.progress = None
# Check Updates Timer # Check Updates Timer
timer = QtCore.QTimer(self) timer = QtCore.QTimer(self)
timer.timeout.connect(self.check_updates) timer.timeout.connect(self.check_updates)
timer.start(1000 * 30) # 30s timer.start(1000 * 30) # 30s
self.check_updates() self.check_updates()
def keyPressEvent(self, event): # pylint: disable=invalid-name
if event.key() == QtCore.Qt.Key_Escape:
self.searchbox.clear()
super(VmManagerWindow, self).keyPressEvent(event)
def clear_threads(self):
for thread in self.threads_list:
if thread.isFinished():
if self.progress:
self.progress.hide()
self.progress = None
if thread.msg:
(title, msg) = thread.msg
QtGui.QMessageBox.warning(
None,
self.tr(title),
self.tr(msg))
self.threads_list.remove(thread)
return
raise RuntimeError('No finished thread found')
def closeEvent(self, event): def closeEvent(self, event):
# pylint: disable=invalid-name # pylint: disable=invalid-name
# save window size at close # save window size at close
@ -433,21 +515,19 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
pass pass
def on_domain_added(self, _submitter, _event, vm, **_kwargs): def on_domain_added(self, _submitter, _event, vm, **_kwargs):
row_no = 0
self.table.setSortingEnabled(False) self.table.setSortingEnabled(False)
try:
row_no = self.table.rowCount() domain = self.qubes_app.domains[vm]
self.table.setRowCount(row_no + 1) row_no = self.table.rowCount()
self.table.setRowCount(row_no + 1)
for domain in self.qubes_app.domains: vm_row = VmRowInTable(domain, row_no, self.table)
if domain == vm: self.vms_in_table[domain.qid] = vm_row
vm_row = VmRowInTable(domain, row_no, self.table) except (exc.QubesException, KeyError):
self.vms_in_table[domain.qid] = vm_row if row_no != 0:
self.table.setSortingEnabled(True) self.table.removeRow(row_no)
self.showhide_vms() self.table.setSortingEnabled(True)
return self.showhide_vms()
# Never should reach here
raise RuntimeError('Added domain not found')
def on_domain_removed(self, _submitter, _event, **kwargs): def on_domain_removed(self, _submitter, _event, **kwargs):
row_to_delete = None row_to_delete = None
@ -520,17 +600,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
return [vm for vm in self.qubes_app.domains] return [vm for vm in self.qubes_app.domains]
def fill_table(self): def fill_table(self):
progress = QtGui.QProgressDialog(
self.tr(
"Loading Qube Manager..."), "", 0, 0)
progress.setWindowTitle(self.tr("Qube Manager"))
progress.setWindowFlags(QtCore.Qt.Window |
QtCore.Qt.WindowTitleHint |
QtCore.Qt.CustomizeWindowHint)
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
self.table.setSortingEnabled(False) self.table.setSortingEnabled(False)
vms_list = self.get_vms_list() vms_list = self.get_vms_list()
@ -538,19 +607,26 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.table.setRowCount(len(vms_list)) self.table.setRowCount(len(vms_list))
progress = QtGui.QProgressDialog(
self.tr(
"Loading Qube Manager..."), "", 0, len(vms_list))
progress.setWindowTitle(self.tr("Qube Manager"))
progress.setMinimumDuration(1000)
progress.setCancelButton(None)
row_no = 0 row_no = 0
for vm in vms_list: for vm in vms_list:
progress.setValue(row_no)
vm_row = VmRowInTable(vm, row_no, self.table) vm_row = VmRowInTable(vm, row_no, self.table)
vms_in_table[vm.qid] = vm_row vms_in_table[vm.qid] = vm_row
row_no += 1 row_no += 1
self.qt_app.processEvents()
progress.setValue(row_no)
self.vms_list = vms_list self.vms_list = vms_list
self.vms_in_table = vms_in_table self.vms_in_table = vms_in_table
self.table.setSortingEnabled(True) self.table.setSortingEnabled(True)
progress.hide()
def showhide_vms(self): def showhide_vms(self):
if not self.search: if not self.search:
for row_no in range(self.table.rowCount()): for row_no in range(self.table.rowCount()):
@ -584,11 +660,9 @@ 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:
# TODO: add boot from device to menu and add windows tools there # TODO: add boot from device to menu and add windows tools there
# Update available actions: # Update available actions:
self.action_settings.setEnabled(vm.klass != 'AdminVM') self.action_settings.setEnabled(vm.klass != 'AdminVM')
@ -640,7 +714,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
# noinspection PyArgumentList # noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_createvm_triggered') @QtCore.pyqtSlot(name='on_action_createvm_triggered')
def action_createvm_triggered(self): # pylint: disable=no-self-use def action_createvm_triggered(self): # pylint: disable=no-self-use
subprocess.check_call('qubes-vm-create') create_window = create_new_vm.NewVmDlg(self.qt_app, self.qubes_app)
create_window.exec_()
def get_selected_vm(self): def get_selected_vm(self):
# vm selection relies on the VmInfo widget's value used # vm selection relies on the VmInfo widget's value used
@ -681,7 +756,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
"or setting that uses it.</small>").format(list_text)) "or setting that uses it.</small>").format(list_text))
info_dialog.setModal(False) info_dialog.setModal(False)
info_dialog.show() info_dialog.show()
self.qt_app.processEvents()
return return
@ -708,44 +782,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
else: else:
# remove the VM # remove the VM
t_monitor = thread_monitor.ThreadMonitor() thread = common_threads.RemoveVMThread(vm)
thread = threading.Thread(target=self.do_remove_vm, self.threads_list.append(thread)
args=(vm, self.qubes_app, t_monitor)) thread.finished.connect(self.clear_threads)
thread.daemon = True
thread.start() thread.start()
progress = QtGui.QProgressDialog(
self.tr(
"Removing Qube: <b>{0}</b>...").format(vm.name), "", 0, 0)
progress.setWindowFlags(QtCore.Qt.Window |
QtCore.Qt.WindowTitleHint |
QtCore.Qt.CustomizeWindowHint)
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
while not t_monitor.is_finished():
self.qt_app.processEvents()
time.sleep(0.1)
progress.hide()
if t_monitor.success:
pass
else:
QtGui.QMessageBox.warning(None, self.tr("Error removing Qube!"),
self.tr("ERROR: {0}").format(
t_monitor.error_msg))
@staticmethod
def do_remove_vm(vm, qubes_app, t_monitor):
try:
del qubes_app.domains[vm.name]
except exc.QubesException as ex:
t_monitor.set_error_msg(str(ex))
t_monitor.set_finished()
# noinspection PyArgumentList # noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_clonevm_triggered') @QtCore.pyqtSlot(name='on_action_clonevm_triggered')
def action_clonevm_triggered(self): def action_clonevm_triggered(self):
@ -763,48 +804,18 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if not ok or clone_name == "": if not ok or clone_name == "":
return return
t_monitor = thread_monitor.ThreadMonitor() self.progress = QtGui.QProgressDialog(
thread = threading.Thread(target=self.do_clone_vm, self.tr(
args=(vm, self.qubes_app, "Cloning Qube..."), "", 0, 0)
clone_name, t_monitor)) self.progress.setCancelButton(None)
thread.daemon = True self.progress.setModal(True)
self.progress.show()
thread = common_threads.CloneVMThread(vm, clone_name)
thread.finished.connect(self.clear_threads)
self.threads_list.append(thread)
thread.start() thread.start()
progress = QtGui.QProgressDialog(
self.tr("Cloning Qube <b>{0}</b> to <b>{1}</b>...").format(
vm.name, clone_name), "", 0, 0)
progress.setWindowFlags(QtCore.Qt.Window |
QtCore.Qt.WindowTitleHint |
QtCore.Qt.CustomizeWindowHint)
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
while not t_monitor.is_finished():
self.qt_app.processEvents()
time.sleep(0.2)
progress.hide()
if not t_monitor.success:
QtGui.QMessageBox.warning(
None,
self.tr("Error while cloning Qube"),
self.tr("Exception while cloning:<br>{0}").format(
t_monitor.error_msg))
@staticmethod
def do_clone_vm(src_vm, qubes_app, dst_name, t_monitor):
dst_vm = None
try:
dst_vm = qubes_app.clone_vm(src_vm, dst_name)
except exc.QubesException as ex:
t_monitor.set_error_msg(str(ex))
if dst_vm:
pass
t_monitor.set_finished()
# noinspection PyArgumentList # noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_resumevm_triggered') @QtCore.pyqtSlot(name='on_action_resumevm_triggered')
def action_resumevm_triggered(self): def action_resumevm_triggered(self):
@ -813,8 +824,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if vm.get_power_state() in ["Paused", "Suspended"]: 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!"),
@ -826,34 +835,12 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
def start_vm(self, vm): def start_vm(self, vm):
if vm.is_running(): if vm.is_running():
return return
t_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(target=self.do_start_vm, thread = StartVMThread(vm)
args=(vm, t_monitor)) self.threads_list.append(thread)
thread.daemon = True thread.finished.connect(self.clear_threads)
thread.start() thread.start()
while not t_monitor.is_finished():
self.qt_app.processEvents()
time.sleep(0.1)
if not t_monitor.success:
QtGui.QMessageBox.warning(
None,
self.tr("Error starting Qube!"),
self.tr("ERROR: {0}").format(t_monitor.error_msg))
@staticmethod
def do_start_vm(vm, t_monitor):
try:
vm.start()
except exc.QubesException as ex:
t_monitor.set_error_msg(str(ex))
t_monitor.set_finished()
return
t_monitor.set_finished()
# noinspection PyArgumentList # noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_startvm_tools_install_triggered') @QtCore.pyqtSlot(name='on_action_startvm_tools_install_triggered')
# TODO: replace with boot from device # TODO: replace with boot from device
@ -866,8 +853,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
vm = self.get_selected_vm() vm = self.get_selected_vm()
try: try:
vm.pause() vm.pause()
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, None,
@ -885,9 +870,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.tr("Are you sure you want to power down the Qube" self.tr("Are you sure you want to power down the Qube"
" <b>'{0}'</b>?<br><small>This will shutdown all the " " <b>'{0}'</b>?<br><small>This will shutdown all the "
"running applications within this Qube.</small>").format( "running applications within this Qube.</small>").format(
vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
self.qt_app.processEvents()
if reply == QtGui.QMessageBox.Yes: if reply == QtGui.QMessageBox.Yes:
self.shutdown_vm(vm) self.shutdown_vm(vm)
@ -922,8 +905,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
"applications within this Qube.</small>").format(vm.name), "applications within this Qube.</small>").format(vm.name),
QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
self.qt_app.processEvents()
if reply == QtGui.QMessageBox.Yes: if reply == QtGui.QMessageBox.Yes:
# in case the user shut down the VM in the meantime # in case the user shut down the VM in the meantime
if vm.is_running(): if vm.is_running():
@ -938,7 +919,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if not (vm.is_running() or vm.is_paused()): if not (vm.is_running() or vm.is_paused()):
info = self.tr("Qube <b>'{0}'</b> is not running. Are you " info = self.tr("Qube <b>'{0}'</b> is not running. Are you "
"absolutely sure you want to try to kill it?<br>" "absolutely sure you want to try to kill it?<br>"
"<small>This will end <b>(not shutdown!)</b> all " "<small>This will end <b>(not shutdown!)</b> all "
"the running applications within this " "the running applications within this "
"Qube.</small>").format(vm.name) "Qube.</small>").format(vm.name)
else: else:
@ -952,8 +933,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel,
QtGui.QMessageBox.Cancel) QtGui.QMessageBox.Cancel)
self.qt_app.processEvents()
if reply == QtGui.QMessageBox.Yes: if reply == QtGui.QMessageBox.Yes:
try: try:
vm.kill() vm.kill()
@ -1017,55 +996,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if reply != QtGui.QMessageBox.Yes: if reply != QtGui.QMessageBox.Yes:
return return
self.qt_app.processEvents() thread = UpdateVMThread(vm)
self.threads_list.append(thread)
t_monitor = thread_monitor.ThreadMonitor() thread.finished.connect(self.clear_threads)
thread = threading.Thread(target=self.do_update_vm,
args=(vm, t_monitor))
thread.daemon = True
thread.start() thread.start()
progress = QtGui.QProgressDialog(
self.tr(
"<b>{0}</b><br>Please wait for the updater to "
"launch...").format(vm.name), "", 0, 0)
progress.setWindowFlags(QtCore.Qt.Window |
QtCore.Qt.WindowTitleHint |
QtCore.Qt.CustomizeWindowHint)
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
while not t_monitor.is_finished():
self.qt_app.processEvents()
time.sleep(0.2)
progress.hide()
if vm.qid != 0:
if not t_monitor.success:
QtGui.QMessageBox.warning(
None,
self.tr("Error on Qube update!"),
self.tr("ERROR: {0}").format(t_monitor.error_msg))
@staticmethod
def do_update_vm(vm, t_monitor):
try:
if vm.qid == 0:
subprocess.check_call(
["/usr/bin/qubes-dom0-update", "--clean", "--gui"])
else:
if not vm.is_running():
vm.start()
vm.run_service("qubes.InstallUpdatesGUI",
user="root", wait=False)
except (ChildProcessError, exc.QubesException) as ex:
t_monitor.set_error_msg(str(ex))
t_monitor.set_finished()
return
t_monitor.set_finished()
# noinspection PyArgumentList # noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_run_command_in_vm_triggered') @QtCore.pyqtSlot(name='on_action_run_command_in_vm_triggered')
@ -1078,30 +1013,12 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.tr('Run command in <b>{}</b>:').format(vm.name)) self.tr('Run command in <b>{}</b>:').format(vm.name))
if not ok or command_to_run == "": if not ok or command_to_run == "":
return return
t_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(target=self.do_run_command_in_vm, args=( thread = RunCommandThread(vm, command_to_run)
vm, command_to_run, t_monitor)) self.threads_list.append(thread)
thread.daemon = True thread.finished.connect(self.clear_threads)
thread.start() thread.start()
while not t_monitor.is_finished():
self.qt_app.processEvents()
time.sleep(0.2)
if not t_monitor.success:
QtGui.QMessageBox.warning(
None, self.tr("Error while running command"),
self.tr("Exception while running command:<br>{0}").format(
t_monitor.error_msg))
@staticmethod
def do_run_command_in_vm(vm, command_to_run, t_monitor):
try:
vm.run(command_to_run)
except (ChildProcessError, exc.QubesException) as ex:
t_monitor.set_error_msg(str(ex))
t_monitor.set_finished()
# noinspection PyArgumentList # 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):
@ -1141,8 +1058,9 @@ 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,
backup_window.exec_() self.dispatcher, self)
backup_window.show()
# noinspection PyArgumentList # noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_exit_triggered') @QtCore.pyqtSlot(name='on_action_exit_triggered')

View File

@ -23,31 +23,56 @@
import sys import sys
from PyQt4 import QtCore # pylint: disable=import-error from PyQt4 import QtCore # pylint: disable=import-error
from PyQt4 import QtGui # pylint: disable=import-error from PyQt4 import QtGui # pylint: disable=import-error
import threading
import time
import os import os
import os.path import os.path
import traceback import traceback
import logging import logging
import logging.handlers import logging.handlers
import signal
from qubes import backup 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
from multiprocessing.queues import Empty from multiprocessing.queues import Empty
from qubesadmin import Qubes, exc from qubesadmin import Qubes, exc
from qubesadmin.backup import restore from qubesadmin.backup import restore
class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard): # pylint: disable=too-few-public-methods
class RestoreThread(QtCore.QThread):
def __init__(self, backup_restore, vms_to_restore):
QtCore.QThread.__init__(self)
self.backup_restore = backup_restore
self.vms_to_restore = vms_to_restore
self.msg = None
self.canceled = None
def run(self):
err_msg = []
try:
self.backup_restore.restore_do(self.vms_to_restore)
except backup.BackupCanceledError as ex:
self.canceled = True
err_msg.append(str(ex))
except Exception as ex: # pylint: disable=broad-except
err_msg.append(str(ex))
err_msg.append(
self.tr("Partially restored files left in /var/tmp/restore_*, "
"investigate them and/or clean them up"))
if err_msg:
self.msg = '\n'.join(err_msg)
self.msg = '<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)
@ -57,6 +82,8 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
self.vms_to_restore = None self.vms_to_restore = None
self.func_output = [] self.func_output = []
self.thread = None
# Set up logging # Set up logging
self.feedback_queue = Queue() self.feedback_queue = Queue()
handler = logging.handlers.QueueHandler(self.feedback_queue) handler = logging.handlers.QueueHandler(self.feedback_queue)
@ -64,9 +91,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
@ -144,41 +168,12 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
self.select_vms_widget.available_list.addItem(vmname) self.select_vms_widget.available_list.addItem(vmname)
except exc.QubesException as ex: except exc.QubesException as ex:
QtGui.QMessageBox.warning(None, self.tr("Restore error!"), str(ex)) QtGui.QMessageBox.warning(None, self.tr("Restore error!"), str(ex))
self.restart()
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
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,51 +205,56 @@ 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,
thread = threading.Thread(target=self.__do_restore__, self.vms_to_restore)
args=(self.thread_monitor,)) self.thread.finished.connect(self.thread_finished)
thread.daemon = True
thread.start()
while not self.thread_monitor.is_finished():
self.qt_app.processEvents()
time.sleep(0.1)
try:
log_record = self.feedback_queue.get_nowait()
while log_record:
if log_record.levelno == logging.ERROR or\
log_record.levelno == logging.CRITICAL:
output = '<font color="red">{0}</font>'.format(
log_record.getMessage())
else:
output = log_record.getMessage()
self.append_output(output)
log_record = self.feedback_queue.get_nowait()
except Empty:
pass
if not self.thread_monitor.success: # Start log timer
if not self.canceled: timer = QtCore.QTimer(self)
QtGui.QMessageBox.warning( timer.timeout.connect(self.update_log)
None, timer.start(1000)
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.thread.start()
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) def thread_finished(self):
self.button(self.CancelButton).setEnabled(False) self.progress_bar.setMaximum(100)
self.showFileDialog.setEnabled(False) self.progress_bar.setValue(100)
if self.thread.msg:
QtGui.QMessageBox.warning(
None,
self.tr("Restore qubes"),
self.tr(self.thread.msg))
if self.thread.msg:
self.append_output(self.thread.msg)
if self.showFileDialog.isChecked():
self.append_output(
'<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)
def update_log(self):
try:
log_record = self.feedback_queue.get_nowait()
while log_record:
if log_record.levelno == logging.ERROR or\
log_record.levelno == logging.CRITICAL:
output = '<font color="red">{0}</font>'.format(
log_record.getMessage())
else:
output = log_record.getMessage()
self.append_output(output)
log_record = self.feedback_queue.get_nowait()
except Empty:
pass
signal.signal(signal.SIGCHLD, old_sigchld_handler)
def all_vms_good(self): def all_vms_good(self):
for vm_info in self.vms_to_restore.values(): for vm_info in self.vms_to_restore.values():
@ -265,7 +265,7 @@ 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...")))

View File

@ -27,8 +27,6 @@ import os.path
import os import os
import re import re
import subprocess import subprocess
import threading
import time
import traceback import traceback
import sys import sys
from qubesadmin.tools import QubesArgumentParser from qubesadmin.tools import QubesArgumentParser
@ -38,7 +36,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
@ -47,16 +45,90 @@ 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
# pylint: disable=too-few-public-methods
class RenameVMThread(QtCore.QThread):
def __init__(self, vm, new_vm_name, dependencies):
QtCore.QThread.__init__(self)
self.vm = vm
self.new_vm_name = new_vm_name
self.dependencies = dependencies
self.msg = None
def run(self):
try:
new_vm = self.vm.app.clone_vm(self.vm, self.new_vm_name)
failed_props = []
for (holder, prop) in self.dependencies:
try:
if holder is None:
setattr(self.vm.app, prop, new_vm)
else:
setattr(holder, prop, new_vm)
except qubesadmin.exc.QubesException:
failed_props += [(holder, prop)]
if not failed_props:
del self.vm.app.domains[self.vm.name]
else:
list_text = utils.format_dependencies_list(failed_props)
QtGui.QMessageBox.warning(
self,
self.tr("Warning: rename partially unsuccessful"),
self.tr("Some properties could not be changed to the new "
"name. The system has now both {} and {} qubes. "
"To resolve this, please check and change the "
"following properties and remove the qube {} "
"manually.<br> ").format(
self.vm.name, self.vm.name, self.vm.name)\
+ list_text)
except qubesadmin.exc.QubesException as ex:
self.msg = ("Rename error!", str(ex))
except Exception as ex: # pylint: disable=broad-except
self.msg = ("Rename error!", repr(ex))
# pylint: disable=too-few-public-methods
class RefreshAppsVMThread(QtCore.QThread):
def __init__(self, vm):
QtCore.QThread.__init__(self)
self.vm = vm
self.msg = None
def run(self):
try:
try:
target_vm = self.vm.template
except AttributeError:
target_vm = self.vm
if not target_vm.is_running():
not_running = True
target_vm.start()
else:
not_running = False
subprocess.check_call(['qvm-sync-appmenus', target_vm.name])
if not_running:
target_vm.shutdown()
except Exception as ex: # pylint: disable=broad-except
self.msg = ("Refresh failed!", str(ex))
# pylint: disable=too-many-instance-attributes # 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((
('basic', 0), ('basic', 0),
('advanced', 1), ('advanced', 1),
('firewall', 2), ('firewall', 2),
('devices', 3), ('devices', 3),
('applications', 4), ('applications', 4),
('services', 5), ('services', 5),
)) ))
def __init__(self, vm, qapp, init_page="basic", parent=None): def __init__(self, vm, qapp, init_page="basic", parent=None):
@ -64,6 +136,9 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.vm = vm self.vm = vm
self.qapp = qapp self.qapp = qapp
self.threads_list = []
self.progress = None
self.thread_closes = False
try: try:
self.source_vm = self.vm.template self.source_vm = self.vm.template
except AttributeError: except AttributeError:
@ -149,6 +224,29 @@ 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 self.progress:
self.progress.hide()
self.progress = None
if thread.msg:
(title, msg) = thread.msg
QtGui.QMessageBox.warning(
None,
self.tr(title),
self.tr(msg))
self.threads_list.remove(thread)
if self.thread_closes:
self.done(0)
return
raise RuntimeError('No finished thread found')
def keyPressEvent(self, event): # pylint: disable=invalid-name 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:
@ -163,31 +261,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(error)))
def apply(self): def apply(self):
self.save_changes() self.save_changes()
@ -196,9 +277,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:
@ -236,12 +317,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
@ -259,18 +336,17 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
'please enable networking.') 'please enable networking.')
) )
if netvm is not None and \ if netvm is not None and \
not netvm.features.check_with_template( not netvm.features.check_with_template(\
'qubes-firewall', 'qubes-firewall', False):
False):
QtGui.QMessageBox.warning( QtGui.QMessageBox.warning(
self, self,
self.tr("Qube configuration problem!"), self.tr("Qube configuration problem!"),
self.tr("The '{vm}' qube is network connected to " self.tr("The '{vm}' qube is network connected to "\
"'{netvm}', which does not support firewall!<br/>" "'{netvm}', which does not support firewall!<br/>"\
"You may edit the '{vm}' qube firewall rules, but " "You may edit the '{vm}' qube firewall rules, but "\
"these will not take any effect until you connect it " "these will not take any effect until you connect it "\
"to a working Firewall qube.").format( "to a working Firewall qube.").format(\
vm=self.vm.name, netvm=netvm.name)) vm=self.vm.name, netvm=netvm.name))
def current_tab_changed(self, idx): def current_tab_changed(self, idx):
if idx == self.tabs_indices["firewall"]: if idx == self.tabs_indices["firewall"]:
@ -463,61 +539,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)
@ -543,19 +564,19 @@ 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): self.progress = QtGui.QProgressDialog(
try: self.tr(
del self.vm.app.domains[self.vm.name] "Renaming Qube..."), "", 0, 0)
self.progress.setCancelButton(None)
self.progress.setModal(True)
self.thread_closes = True
self.progress.show()
except qubesadmin.exc.QubesException as qex: thread.start()
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):
@ -583,7 +604,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:
@ -592,17 +614,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr("Removal cancelled"), self.tr("Removal cancelled"),
self.tr("The qube will not be removed.")) self.tr("The qube will not be removed."))
def _clone_vm(self, t_monitor, name):
try:
self.vm.app.clone_vm(self.vm, name)
except qubesadmin.exc.QubesException as qex:
t_monitor.set_error_msg(str(qex))
except Exception as ex: # pylint: disable=broad-except
t_monitor.set_error_msg(repr(ex))
t_monitor.set_finished()
def clone_vm(self): def clone_vm(self):
cloned_vm_name, ok = QtGui.QInputDialog.getText( cloned_vm_name, ok = QtGui.QInputDialog.getText(
@ -611,11 +622,19 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr('Name for the cloned qube:')) 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"),
self.tr("The qube was cloned successfully.")) self.progress = QtGui.QProgressDialog(
self.tr(
"Cloning Qube..."), "", 0, 0)
self.progress.setCancelButton(None)
self.progress.setModal(True)
self.thread_closes = True
self.progress.show()
thread.start()
######### advanced tab ######### advanced tab
@ -766,8 +785,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.virt_mode.clear() self.virt_mode.clear()
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
self.virt_mode_list, self.virt_mode_idx = utils.prepare_choice( self.virt_mode_list, self.virt_mode_idx = utils.prepare_choice(\
self.virt_mode, self.vm, 'virt_mode', choices, None, self.virt_mode, self.vm, 'virt_mode', choices, None,\
allow_default=True, transform=(lambda x: str(x).upper())) allow_default=True, transform=(lambda x: str(x).upper()))
if old_mode is not None: if old_mode is not None:
@ -958,43 +977,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()

View File

@ -89,7 +89,7 @@ rm -rf $RPM_BUILD_ROOT
%{python3_sitelib}/qubesmanager/releasenotes.py %{python3_sitelib}/qubesmanager/releasenotes.py
%{python3_sitelib}/qubesmanager/informationnotes.py %{python3_sitelib}/qubesmanager/informationnotes.py
%{python3_sitelib}/qubesmanager/create_new_vm.py %{python3_sitelib}/qubesmanager/create_new_vm.py
%{python3_sitelib}/qubesmanager/thread_monitor.py %{python3_sitelib}/qubesmanager/common_threads.py
%{python3_sitelib}/qubesmanager/qube_manager.py %{python3_sitelib}/qubesmanager/qube_manager.py
%{python3_sitelib}/qubesmanager/utils.py %{python3_sitelib}/qubesmanager/utils.py
%{python3_sitelib}/qubesmanager/bootfromdevice.py %{python3_sitelib}/qubesmanager/bootfromdevice.py