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:
commit
334fefe559
@ -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 \
|
||||
|
@ -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_()
|
||||
|
@ -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__":
|
||||
|
54
qubesmanager/common_threads.py
Normal file
54
qubesmanager/common_threads.py
Normal 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))
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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...")))
|
||||
|
@ -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'))
|
||||
|
||||
|
@ -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()
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user