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/clipboard.py \
qubesmanager/create_new_vm.py \
qubesmanager/common_threads.py \
qubesmanager/firewall.py \
qubesmanager/global_settings.py \
qubesmanager/log_dialog.py \
@ -27,7 +28,6 @@ SOURCES = \
qubesmanager/restore.py \
qubesmanager/settings.py \
qubesmanager/table_widgets.py \
qubesmanager/thread_monitor.py \
qubesmanager/ui_about.py \
qubesmanager/ui_backupdlg.py \
qubesmanager/ui_globalsettingsdlg.py \

View File

@ -20,7 +20,6 @@
# 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 qubesmanager.releasenotes import ReleaseNotesDialog
from qubesmanager.informationnotes import InformationNotesDialog
@ -28,6 +27,7 @@ from qubesmanager.informationnotes import InformationNotesDialog
from . import ui_about # pylint: disable=no-name-in-module
# pylint: disable=too-few-public-methods
class AboutDialog(ui_about.Ui_AboutDialog, QDialog):
def __init__(self):
super(AboutDialog, self).__init__()
@ -38,18 +38,14 @@ class AboutDialog(ui_about.Ui_AboutDialog, QDialog):
with open('/etc/qubes-release', 'r') as release_file:
self.release.setText(release_file.read())
self.connect(self.ok, SIGNAL("clicked()"), SLOT("accept()"))
self.connect(self.releaseNotes, SIGNAL("clicked()"),
self.on_release_notes_clicked)
self.connect(self.informationNotes, SIGNAL("clicked()"),
self.on_information_notes_clicked)
self.ok.clicked.connect(self.accept)
self.releaseNotes.clicked.connect(on_release_notes_clicked)
self.informationNotes.clicked.connect(on_information_notes_clicked)
def on_release_notes_clicked(self):
release_notes_dialog = ReleaseNotesDialog()
release_notes_dialog.exec_()
self.accept()
def on_release_notes_clicked():
release_notes_dialog = ReleaseNotesDialog()
release_notes_dialog.exec_()
def on_information_notes_clicked(self):
information_notes_dialog = InformationNotesDialog()
information_notes_dialog.exec_()
self.accept()
def on_information_notes_clicked():
information_notes_dialog = InformationNotesDialog()
information_notes_dialog.exec_()

View File

@ -23,9 +23,11 @@
import traceback
import signal
import quamash
from qubesadmin import Qubes, exc
from qubesadmin import utils as admin_utils
from qubesadmin import events
from qubes.storage.file import get_disk_usage
from PyQt4 import QtCore # pylint: disable=import-error
@ -35,18 +37,38 @@ from . import multiselectwidget
from . import backup_utils
from . import utils
import grp
import pwd
import sys
import os
from . import thread_monitor
import threading
import time
import asyncio
from contextlib import suppress
# pylint: disable=too-few-public-methods
class BackupThread(QtCore.QThread):
def __init__(self, vm):
QtCore.QThread.__init__(self)
self.vm = vm
self.msg = None
def run(self):
msg = []
try:
if not self.vm.is_running():
self.vm.start()
self.vm.app.qubesd_call(
'dom0', 'admin.backup.Execute',
backup_utils.get_profile_name(True))
except Exception as ex: # pylint: disable=broad-except
msg.append(str(ex))
if msg:
self.msg = '\n'.join(msg)
class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
def __init__(self, qt_app, qubes_app, parent=None):
def __init__(self, qt_app, qubes_app, dispatcher, parent=None):
super(BackupVMsWindow, self).__init__(parent)
self.qt_app = qt_app
@ -54,8 +76,7 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
self.backup_settings = QtCore.QSettings()
self.selected_vms = []
self.canceled = False
self.thread_monitor = None
self.thread = None
self.setupUi(self)
@ -112,6 +133,16 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
selected = self.load_settings()
self.__fill_vms_list__(selected)
# Connect backup events for progress_bar
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(100)
self.dispatcher = dispatcher
dispatcher.add_handler('backup-progress', self.on_backup_progress)
def on_backup_progress(self, __submitter, _event, **kwargs):
self.progress_bar.setValue(int(float(kwargs['progress'])))
def load_settings(self):
"""
Helper function that tries to load existing backup profile
@ -260,24 +291,6 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
return True
def __do_backup__(self, t_monitor):
msg = []
try:
vm = self.qubes_app.domains[
self.appvm_combobox.currentText()]
if not vm.is_running():
vm.start()
self.qubes_app.qubesd_call(
'dom0', 'admin.backup.Execute',
backup_utils.get_profile_name(True))
except Exception as ex: # pylint: disable=broad-except
msg.append(str(ex))
if msg:
t_monitor.set_error_msg('\n'.join(msg))
t_monitor.set_finished()
@staticmethod
def cleanup_temporary_files():
@ -310,33 +323,27 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
self.showFileDialog.setChecked(self.showFileDialog.isEnabled()
and str(self.dir_line_edit.text())
.count("media/") > 0)
self.thread_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(
target=self.__do_backup__,
args=(self.thread_monitor,))
thread.daemon = True
thread.start()
while not self.thread_monitor.is_finished():
self.qt_app.processEvents()
time.sleep(0.1)
vm = self.qubes_app.domains[
self.appvm_combobox.currentText()]
self.thread = BackupThread(vm)
self.thread.finished.connect(self.backup_finished)
self.thread.start()
signal.signal(signal.SIGCHLD, old_sigchld_handler)
def backup_finished(self):
if self.thread.msg:
self.progress_status.setText(self.tr("Backup error."))
QtGui.QMessageBox.warning(
self, self.tr("Backup error!"),
self.tr("ERROR: {}").format(
self.thread.msg))
else:
self.progress_bar.setValue(100)
self.progress_status.setText(self.tr("Backup finished."))
if not self.thread_monitor.success:
if self.canceled:
self.progress_status.setText(
self.tr(
"Backup aborted. "
"Temporary file may be left at backup location."))
else:
self.progress_status.setText(self.tr("Backup error."))
QtGui.QMessageBox.warning(
self, self.tr("Backup error!"),
self.tr("ERROR: {}").format(
self.thread_monitor.error_msg))
else:
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(100)
self.progress_status.setText(self.tr("Backup finished."))
if self.showFileDialog.isChecked():
orig_text = self.progress_status.text
self.progress_status.setText(
@ -344,31 +351,28 @@ class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
" Please unmount your backup volume and cancel "
"the file selection dialog."))
backup_utils.select_path_button_clicked(self, False, True)
self.button(self.CancelButton).setEnabled(False)
self.button(self.FinishButton).setEnabled(True)
self.showFileDialog.setEnabled(False)
self.cleanup_temporary_files()
# turn off only when backup was successful
if self.thread_monitor.success and \
self.turn_off_checkbox.isChecked():
if self.turn_off_checkbox.isChecked():
os.system('systemctl poweroff')
signal.signal(signal.SIGCHLD, old_sigchld_handler)
def reject(self):
if self.currentPage() is self.commit_page:
self.canceled = True
self.qubes_app.qubesd_call(
'dom0', 'admin.backup.Cancel',
backup_utils.get_profile_name(True))
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(0)
self.button(self.CancelButton).setDisabled(True)
self.cleanup_temporary_files()
else:
self.cleanup_temporary_files()
self.done(0)
self.thread.wait()
QtGui.QMessageBox.warning(
self, self.tr("Backup aborted!"),
self.tr("ERROR: {}").format("Aborted!"))
self.cleanup_temporary_files()
self.done(0)
def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0
@ -402,9 +406,14 @@ def handle_exception(exc_type, exc_value, exc_traceback):
error + "at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
% (line, filename))
def loop_shutdown():
pending = asyncio.Task.all_tasks()
for task in pending:
with suppress(asyncio.CancelledError):
task.cancel()
def main():
qt_app = QtGui.QApplication(sys.argv)
qt_app.setOrganizationName("The Qubes Project")
qt_app.setOrganizationDomain("http://qubes-os.org")
@ -412,14 +421,24 @@ def main():
sys.excepthook = handle_exception
app = Qubes()
qubes_app = Qubes()
backup_window = BackupVMsWindow(qt_app, app)
loop = quamash.QEventLoop(qt_app)
asyncio.set_event_loop(loop)
dispatcher = events.EventsDispatcher(qubes_app)
backup_window = BackupVMsWindow(qt_app, qubes_app, dispatcher)
backup_window.show()
qt_app.exec_()
qt_app.exit()
try:
loop.run_until_complete(
asyncio.ensure_future(dispatcher.listen_for_events()))
except asyncio.CancelledError:
pass
except Exception: # pylint: disable=broad-except
loop_shutdown()
exc_type, exc_value, exc_traceback = sys.exc_info()[:3]
handle_exception(exc_type, exc_value, exc_traceback)
if __name__ == "__main__":

View File

@ -0,0 +1,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 threading
import time
import subprocess
from PyQt4 import QtCore, QtGui # pylint: disable=import-error
@ -35,7 +33,40 @@ import qubesadmin.exc
from . import utils
from .ui_newappvmdlg import Ui_NewVMDlg # pylint: disable=import-error
from .thread_monitor import ThreadMonitor
# pylint: disable=too-few-public-methods
class CreateVMThread(QtCore.QThread):
def __init__(self, app, vmclass, name, label, template, properties):
QtCore.QThread.__init__(self)
self.app = app
self.vmclass = vmclass
self.name = name
self.label = label
self.template = template
self.properties = properties
self.msg = None
def run(self):
try:
if self.vmclass == 'StandaloneVM' and self.template is not None:
if self.template is qubesadmin.DEFAULT:
src_vm = self.app.default_template
else:
src_vm = self.template
vm = self.app.clone_vm(src_vm, self.name, self.vmclass)
vm.label = self.label
for k, v in self.properties.items():
setattr(vm, k, v)
else:
vm = self.app.add_new_vm(self.vmclass,
name=self.name, label=self.label, template=self.template)
for k, v in self.properties.items():
setattr(vm, k, v)
except qubesadmin.exc.QubesException as qex:
self.msg = str(qex)
except Exception as ex: # pylint: disable=broad-except
self.msg = repr(ex)
class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
@ -46,6 +77,9 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
self.qtapp = qtapp
self.app = app
self.thread = None
self.progress = None
# Theoretically we should be locking for writing here and unlock
# only after the VM creation finished. But the code would be
# more messy...
@ -125,67 +159,37 @@ class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
properties['virt_mode'] = 'hvm'
properties['kernel'] = None
thread_monitor = ThreadMonitor()
thread = threading.Thread(target=self.do_create_vm,
args=(self.app, vmclass, name, label, template, properties,
thread_monitor))
thread.daemon = True
thread.start()
self.thread = CreateVMThread(self.app, vmclass, name, label,
template, properties)
self.thread.finished.connect(self.create_finished)
self.thread.start()
progress = QtGui.QProgressDialog(
self.progress = QtGui.QProgressDialog(
self.tr("Creating new qube <b>{}</b>...").format(name), "", 0, 0)
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
self.progress.setCancelButton(None)
self.progress.setModal(True)
self.progress.show()
while not thread_monitor.is_finished():
self.qtapp.processEvents()
time.sleep(0.1)
def create_finished(self):
self.progress.hide()
progress.hide()
if not thread_monitor.success:
if self.thread.msg:
QtGui.QMessageBox.warning(None,
self.tr("Error creating the qube!"),
self.tr("ERROR: {}").format(thread_monitor.error_msg))
self.tr("ERROR: {}").format(self.thread.msg))
self.done(0)
if thread_monitor.success:
if not self.thread.msg:
if self.launch_settings.isChecked():
subprocess.check_call(['qubes-vm-settings', name])
subprocess.check_call(['qubes-vm-settings',
str(self.name.text())])
if self.install_system.isChecked():
subprocess.check_call(
['qubes-vm-boot-from-device', name])
['qubes-vm-boot-from-device', str(self.name.text())])
@staticmethod
def do_create_vm(app, vmclass, name, label, template, properties,
thread_monitor):
try:
if vmclass == 'StandaloneVM' and template is not None:
if template is qubesadmin.DEFAULT:
src_vm = app.default_template
else:
src_vm = template
vm = app.clone_vm(src_vm, name, vmclass)
vm.label = label
for k, v in properties.items():
setattr(vm, k, v)
else:
vm = app.add_new_vm(vmclass,
name=name, label=label, template=template)
for k, v in properties.items():
setattr(vm, k, v)
except qubesadmin.exc.QubesException as qex:
thread_monitor.set_error_msg(str(qex))
except Exception as ex: # pylint: disable=broad-except
thread_monitor.set_error_msg(repr(ex))
thread_monitor.set_finished()
def type_change(self):
# AppVM
if self.vm_type.currentIndex() == 0:
self.template_vm.setEnabled(True)

View File

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

View File

@ -23,31 +23,56 @@
import sys
from PyQt4 import QtCore # pylint: disable=import-error
from PyQt4 import QtGui # pylint: disable=import-error
import threading
import time
import os
import os.path
import traceback
import logging
import logging.handlers
import signal
from qubes import backup
from . import ui_restoredlg # pylint: disable=no-name-in-module
from . import multiselectwidget
from . import backup_utils
from . import thread_monitor
from multiprocessing import Queue, Event
from multiprocessing import Queue
from multiprocessing.queues import Empty
from qubesadmin import Qubes, exc
from qubesadmin.backup import restore
class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
# pylint: disable=too-few-public-methods
class RestoreThread(QtCore.QThread):
def __init__(self, backup_restore, vms_to_restore):
QtCore.QThread.__init__(self)
self.backup_restore = backup_restore
self.vms_to_restore = vms_to_restore
self.msg = None
self.canceled = None
def run(self):
err_msg = []
try:
self.backup_restore.restore_do(self.vms_to_restore)
except backup.BackupCanceledError as ex:
self.canceled = True
err_msg.append(str(ex))
except Exception as ex: # pylint: disable=broad-except
err_msg.append(str(ex))
err_msg.append(
self.tr("Partially restored files left in /var/tmp/restore_*, "
"investigate them and/or clean them up"))
if err_msg:
self.msg = '\n'.join(err_msg)
self.msg = '<b><font color="red">{0}</font></b>'.format(
self.tr("Finished with errors!"))
else:
self.msg = '<font color="green">{0}</font>'.format(
self.tr("Finished successfully!"))
class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
def __init__(self, qt_app, qubes_app, parent=None):
super(RestoreVMsWindow, self).__init__(parent)
@ -57,6 +82,8 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
self.vms_to_restore = None
self.func_output = []
self.thread = None
# Set up logging
self.feedback_queue = Queue()
handler = logging.handlers.QueueHandler(self.feedback_queue)
@ -64,9 +91,6 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
logger.addHandler(handler)
logger.setLevel(logging.INFO)
self.canceled = False
self.error_detected = Event()
self.thread_monitor = None
self.backup_restore = None
self.target_appvm = None
@ -144,41 +168,12 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
self.select_vms_widget.available_list.addItem(vmname)
except exc.QubesException as ex:
QtGui.QMessageBox.warning(None, self.tr("Restore error!"), str(ex))
self.restart()
def append_output(self, text):
self.commit_text_edit.append(text)
def __do_restore__(self, t_monitor):
err_msg = []
try:
self.backup_restore.restore_do(self.vms_to_restore)
except backup.BackupCanceledError as ex:
self.canceled = True
err_msg.append(str(ex))
except Exception as ex: # pylint: disable=broad-except
err_msg.append(str(ex))
err_msg.append(
self.tr("Partially restored files left in /var/tmp/restore_*, "
"investigate them and/or clean them up"))
if self.canceled:
self.append_output('<b><font color="red">{0}</font></b>'.format(
self.tr("Restore aborted!")))
elif err_msg or self.error_detected.is_set():
if err_msg:
t_monitor.set_error_msg('\n'.join(err_msg))
self.append_output('<b><font color="red">{0}</font></b>'.format(
self.tr("Finished with errors!")))
else:
self.append_output('<font color="green">{0}</font>'.format(
self.tr("Finished successfully!")))
t_monitor.set_finished()
def current_page_changed(self, page_id): # pylint: disable=unused-argument
old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
if self.currentPage() is self.select_vms_page:
self.__fill_vms_list__()
@ -210,51 +205,56 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
and str(self.dir_line_edit.text())
.count("media/") > 0)
self.thread_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(target=self.__do_restore__,
args=(self.thread_monitor,))
thread.daemon = True
thread.start()
while not self.thread_monitor.is_finished():
self.qt_app.processEvents()
time.sleep(0.1)
try:
log_record = self.feedback_queue.get_nowait()
while log_record:
if log_record.levelno == logging.ERROR or\
log_record.levelno == logging.CRITICAL:
output = '<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
self.thread = RestoreThread(self.backup_restore,
self.vms_to_restore)
self.thread.finished.connect(self.thread_finished)
if not self.thread_monitor.success:
if not self.canceled:
QtGui.QMessageBox.warning(
None,
self.tr("Backup error!"),
self.tr("ERROR: {0}").format(
self.thread_monitor.error_msg))
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(100)
# Start log timer
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_log)
timer.start(1000)
if self.showFileDialog.isChecked():
self.append_output(
'<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.thread.start()
self.button(self.FinishButton).setEnabled(True)
self.button(self.CancelButton).setEnabled(False)
self.showFileDialog.setEnabled(False)
def thread_finished(self):
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(100)
if self.thread.msg:
QtGui.QMessageBox.warning(
None,
self.tr("Restore qubes"),
self.tr(self.thread.msg))
if self.thread.msg:
self.append_output(self.thread.msg)
if self.showFileDialog.isChecked():
self.append_output(
'<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):
for vm_info in self.vms_to_restore.values():
@ -265,7 +265,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
return True
def reject(self):
if self.currentPage() is self.commit_page:
if self.currentPage() is self.commit_page and self.thread.isRunning():
self.backup_restore.canceled = True
self.append_output('<font color="red">{0}</font>'.format(
self.tr("Aborting the operation...")))

View File

@ -27,8 +27,6 @@ import os.path
import os
import re
import subprocess
import threading
import time
import traceback
import sys
from qubesadmin.tools import QubesArgumentParser
@ -38,7 +36,7 @@ import qubesadmin.exc
from . import utils
from . import multiselectwidget
from . import thread_monitor
from . import common_threads
from . import device_list
from .appmenu_select import AppmenuSelectManager
@ -47,16 +45,90 @@ from PyQt4 import QtCore, QtGui # pylint: disable=import-error
from . import ui_settingsdlg # pylint: disable=no-name-in-module
# pylint: disable=too-few-public-methods
class RenameVMThread(QtCore.QThread):
def __init__(self, vm, new_vm_name, dependencies):
QtCore.QThread.__init__(self)
self.vm = vm
self.new_vm_name = new_vm_name
self.dependencies = dependencies
self.msg = None
def run(self):
try:
new_vm = self.vm.app.clone_vm(self.vm, self.new_vm_name)
failed_props = []
for (holder, prop) in self.dependencies:
try:
if holder is None:
setattr(self.vm.app, prop, new_vm)
else:
setattr(holder, prop, new_vm)
except qubesadmin.exc.QubesException:
failed_props += [(holder, prop)]
if not failed_props:
del self.vm.app.domains[self.vm.name]
else:
list_text = utils.format_dependencies_list(failed_props)
QtGui.QMessageBox.warning(
self,
self.tr("Warning: rename partially unsuccessful"),
self.tr("Some properties could not be changed to the new "
"name. The system has now both {} and {} qubes. "
"To resolve this, please check and change the "
"following properties and remove the qube {} "
"manually.<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
class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
tabs_indices = collections.OrderedDict((
('basic', 0),
('advanced', 1),
('firewall', 2),
('devices', 3),
('applications', 4),
('services', 5),
('basic', 0),
('advanced', 1),
('firewall', 2),
('devices', 3),
('applications', 4),
('services', 5),
))
def __init__(self, vm, qapp, init_page="basic", parent=None):
@ -64,6 +136,9 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.vm = vm
self.qapp = qapp
self.threads_list = []
self.progress = None
self.thread_closes = False
try:
self.source_vm = self.vm.template
except AttributeError:
@ -149,6 +224,29 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.refresh_apps_button.clicked.connect(
self.refresh_apps_button_pressed)
def clear_threads(self):
for thread in self.threads_list:
if thread.isFinished():
if self.progress:
self.progress.hide()
self.progress = None
if thread.msg:
(title, msg) = thread.msg
QtGui.QMessageBox.warning(
None,
self.tr(title),
self.tr(msg))
self.threads_list.remove(thread)
if self.thread_closes:
self.done(0)
return
raise RuntimeError('No finished thread found')
def keyPressEvent(self, event): # pylint: disable=invalid-name
if event.key() == QtCore.Qt.Key_Enter \
or event.key() == QtCore.Qt.Key_Return:
@ -163,31 +261,14 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
pass
def save_changes(self):
t_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(target=self.__save_changes__,
args=(t_monitor,))
thread.daemon = True
thread.start()
error = self.__save_changes__()
progress = QtGui.QProgressDialog(
self.tr("Applying settings to <b>{0}</b>...").format(self.vm.name),
"", 0, 0)
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
while not t_monitor.is_finished():
self.qapp.processEvents()
time.sleep(0.1)
progress.hide()
if not t_monitor.success:
if error:
QtGui.QMessageBox.warning(
self,
self.tr("Error while changing settings for {0}!"
).format(self.vm.name),
self.tr("ERROR: {0}").format(t_monitor.error_msg))
self.tr("Error while changing settings for {0}!"\
).format(self.vm.name),\
self.tr("ERROR: {0}").format('\n'.join(error)))
def apply(self):
self.save_changes()
@ -196,9 +277,9 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.save_changes()
self.done(0)
def __save_changes__(self, t_monitor):
def __save_changes__(self):
ret = []
try:
ret_tmp = self.__apply_basic_tab__()
if ret_tmp:
@ -236,12 +317,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
except Exception as ex: # pylint: disable=broad-except
ret += [self.tr("Applications tab:"), repr(ex)]
if ret:
t_monitor.set_error_msg('\n'.join(ret))
utils.debug('\n'.join(ret))
t_monitor.set_finished()
return ret
def check_network_availability(self):
netvm = self.vm.netvm
@ -259,18 +336,17 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
'please enable networking.')
)
if netvm is not None and \
not netvm.features.check_with_template(
'qubes-firewall',
False):
not netvm.features.check_with_template(\
'qubes-firewall', False):
QtGui.QMessageBox.warning(
self,
self.tr("Qube configuration problem!"),
self.tr("The '{vm}' qube is network connected to "
"'{netvm}', which does not support firewall!<br/>"
"You may edit the '{vm}' qube firewall rules, but "
"these will not take any effect until you connect it "
"to a working Firewall qube.").format(
vm=self.vm.name, netvm=netvm.name))
self.tr("The '{vm}' qube is network connected to "\
"'{netvm}', which does not support firewall!<br/>"\
"You may edit the '{vm}' qube firewall rules, but "\
"these will not take any effect until you connect it "\
"to a working Firewall qube.").format(\
vm=self.vm.name, netvm=netvm.name))
def current_tab_changed(self, idx):
if idx == self.tabs_indices["firewall"]:
@ -463,61 +539,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
"allowed value."))
self.init_mem.setValue(self.max_mem_size.value() / 10)
def _run_in_thread(self, func, *args):
t_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(target=func, args=(t_monitor, *args,))
thread.daemon = True
thread.start()
while not t_monitor.is_finished():
self.qapp.processEvents()
time.sleep(0.1)
if not t_monitor.success:
QtGui.QMessageBox.warning(self,
self.tr("Error!"),
self.tr("ERROR: {}").format(
t_monitor.error_msg))
return False
return True
def _rename_vm(self, t_monitor, name, dependencies):
try:
new_vm = self.vm.app.clone_vm(self.vm, name)
failed_props = []
for (holder, prop) in dependencies:
try:
if holder is None:
setattr(self.vm.app, prop, new_vm)
else:
setattr(holder, prop, new_vm)
except qubesadmin.exc.QubesException as qex:
failed_props += [(holder, prop)]
if not failed_props:
del self.vm.app.domains[self.vm.name]
else:
list_text = utils.format_dependencies_list(failed_props)
QtGui.QMessageBox.warning(
self,
self.tr("Warning: rename partially unsuccessful"),
self.tr("Some properties could not be changed to the new "
"name. The system has now both {} and {} qubes. "
"To resolve this, please check and change the "
"following properties and remove the qube {} "
"manually.<br> ").format(
self.vm.name, name, self.vm.name) + list_text)
except qubesadmin.exc.QubesException as qex:
t_monitor.set_error_msg(str(qex))
except Exception as ex: # pylint: disable=broad-except
t_monitor.set_error_msg(repr(ex))
t_monitor.set_finished()
def rename_vm(self):
dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm)
@ -543,19 +564,19 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr('New name: (WARNING: all other changes will be discarded)'))
if ok:
if self._run_in_thread(self._rename_vm, new_vm_name, dependencies):
self.done(0)
thread = RenameVMThread(self.vm, new_vm_name, dependencies)
self.threads_list.append(thread)
thread.finished.connect(self.clear_threads)
def _remove_vm(self, t_monitor):
try:
del self.vm.app.domains[self.vm.name]
self.progress = QtGui.QProgressDialog(
self.tr(
"Renaming Qube..."), "", 0, 0)
self.progress.setCancelButton(None)
self.progress.setModal(True)
self.thread_closes = True
self.progress.show()
except qubesadmin.exc.QubesException as qex:
t_monitor.set_error_msg(str(qex))
except Exception as ex: # pylint: disable=broad-except
t_monitor.set_error_msg(repr(ex))
t_monitor.set_finished()
thread.start()
def remove_vm(self):
@ -583,7 +604,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
'qube\'s name below.'))
if ok and answer == self.vm.name:
self._run_in_thread(self._remove_vm)
thread = common_threads.RemoveVMThread(self.vm)
thread.start()
self.done(0)
elif ok:
@ -592,17 +614,6 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr("Removal cancelled"),
self.tr("The qube will not be removed."))
def _clone_vm(self, t_monitor, name):
try:
self.vm.app.clone_vm(self.vm, name)
except qubesadmin.exc.QubesException as qex:
t_monitor.set_error_msg(str(qex))
except Exception as ex: # pylint: disable=broad-except
t_monitor.set_error_msg(repr(ex))
t_monitor.set_finished()
def clone_vm(self):
cloned_vm_name, ok = QtGui.QInputDialog.getText(
@ -611,11 +622,19 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.tr('Name for the cloned qube:'))
if ok:
self._run_in_thread(self._clone_vm, cloned_vm_name)
QtGui.QMessageBox.warning(
self,
self.tr("Success"),
self.tr("The qube was cloned successfully."))
thread = common_threads.CloneVMThread(self.vm, cloned_vm_name)
thread.finished.connect(self.clear_threads)
self.threads_list.append(thread)
self.progress = QtGui.QProgressDialog(
self.tr(
"Cloning Qube..."), "", 0, 0)
self.progress.setCancelButton(None)
self.progress.setModal(True)
self.thread_closes = True
self.progress.show()
thread.start()
######### advanced tab
@ -766,8 +785,8 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
self.virt_mode.clear()
# pylint: disable=attribute-defined-outside-init
self.virt_mode_list, self.virt_mode_idx = utils.prepare_choice(
self.virt_mode, self.vm, 'virt_mode', choices, None,
self.virt_mode_list, self.virt_mode_idx = utils.prepare_choice(\
self.virt_mode, self.vm, 'virt_mode', choices, None,\
allow_default=True, transform=(lambda x: str(x).upper()))
if old_mode is not None:
@ -958,43 +977,19 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
######## applications tab
def refresh_apps_in_vm(self, t_monitor):
try:
target_vm = self.vm.template
except AttributeError:
target_vm = self.vm
if not target_vm.is_running():
not_running = True
target_vm.start()
else:
not_running = False
subprocess.check_call(['qvm-sync-appmenus', target_vm.name])
if not_running:
target_vm.shutdown()
t_monitor.set_finished()
def refresh_apps_button_pressed(self):
self.refresh_apps_button.setEnabled(False)
self.refresh_apps_button.setText(self.tr('Refresh in progress...'))
t_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(
target=self.refresh_apps_in_vm,
args=(t_monitor,))
thread.daemon = True
thread = RefreshAppsVMThread(self.vm)
thread.finished.connect(self.clear_threads)
thread.finished.connect(self.refresh_finished)
self.threads_list.append(thread)
thread.start()
while not t_monitor.is_finished():
self.qapp.processEvents()
time.sleep(0.1)
def refresh_finished(self):
self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list)
self.refresh_apps_button.setEnabled(True)
self.refresh_apps_button.setText(self.tr('Refresh Applications'))

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/informationnotes.py
%{python3_sitelib}/qubesmanager/create_new_vm.py
%{python3_sitelib}/qubesmanager/thread_monitor.py
%{python3_sitelib}/qubesmanager/common_threads.py
%{python3_sitelib}/qubesmanager/qube_manager.py
%{python3_sitelib}/qubesmanager/utils.py
%{python3_sitelib}/qubesmanager/bootfromdevice.py