Merge remote-tracking branch 'qubesos/pr/91'

* qubesos/pr/91: (27 commits)
  Fix long line warning
  Pretty confusion with regex trying to make  travis happy
  Added exceptions for domain add and remove
  Fix pylint and travis errors
  Fix misspelling on Updates Timer() and better timeout
  Added missing updates() method for some widget
  Fix missing template updates-available
  Added startup progress dialog
  More elegant fix for settings size
  Removed debug print
  Outdated state cleanup
  Revert "Removed progress wait when updating template"
  This tries to fix some rare case when it stops receieving dbus events
  Added Timer for template updates
  Refresh table selection when pause/resume
  Added calls to table_selection_changed()
  VmRowInTable dbus events moved to VmManagerWindow
  Set settings dialog to minimun size, it gets adjusted properly to good view
  - Removed unnedeed calls to vms_in_table[vm.qid].update()
  - Removed update_single_row() and add direct calls to update()  (Some of them could be deleted since dbus events will handle them)
  ...
This commit is contained in:
Marek Marczykowski-Górecki 2018-05-22 03:30:21 +02:00
commit 5b752fc55b
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
4 changed files with 196 additions and 121 deletions

View File

@ -29,6 +29,9 @@ import subprocess
import time
from datetime import datetime, timedelta
import traceback
import threading
from pydbus import SessionBus
from qubesadmin import Qubes
from qubesadmin import exc
@ -36,6 +39,8 @@ from qubesadmin import exc
from PyQt4 import QtGui # pylint: disable=import-error
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
@ -44,9 +49,6 @@ from . import global_settings
from . import restore
from . import backup
from . import log_dialog
import threading
from qubesmanager.about import AboutDialog
class SearchBox(QtGui.QLineEdit):
@ -68,13 +70,12 @@ class SearchBox(QtGui.QLineEdit):
class VmRowInTable(object):
# pylint: disable=too-few-public-methods
def __init__(self, vm, row_no, table):
self.vm = vm
self.row_no = row_no
# TODO: replace a various different widgets with a more generic
# VmFeatureWidget or VMPropertyWidget
table_widgets.row_height = VmManagerWindow.row_height
table.setRowHeight(row_no, VmManagerWindow.row_height)
@ -129,6 +130,8 @@ class VmRowInTable(object):
table.setItem(row_no, VmManagerWindow.columns_indices[
'Last backup'], self.last_backup_widget)
self.table = table
def update(self, update_size_on_disk=False):
"""
Update info in a single VM row
@ -136,7 +139,13 @@ class VmRowInTable(object):
widget will extract the data from VM object
:return: None
"""
self.info_widget.update_vm_state(self.vm)
self.info_widget.update_vm_state()
self.template_widget.update()
self.netvm_widget.update()
self.internal_widget.update()
self.ip_widget.update()
self.include_in_backups_widget.update()
self.last_backup_widget.update()
if update_size_on_disk:
self.size_widget.update()
@ -363,16 +372,121 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.load_manager_settings()
# disabling the table for the duration of filling speeds up the process
# immensely. Yes, really.
self.table.setDisabled(True)
self.fill_table()
self.table.setEnabled(True)
self.update_size_on_disk = False
self.shutdown_monitor = {}
# Connect dbus events
self.bus = SessionBus()
manager = self.bus.get("org.qubes.DomainManager1")
manager.DomainAdded.connect(self.on_domain_added)
manager.DomainRemoved.connect(self.on_domain_removed)
manager.Failed.connect(self.on_failed)
manager.Halted.connect(self.on_halted)
manager.Halting.connect(self.on_halting)
manager.Starting.connect(self.on_starting)
manager.Started.connect(self.on_started)
# Check Updates Timer
timer = QtCore.QTimer(self)
timer.timeout.connect(self.check_updates)
timer.start(1000 * 30) # 30s
def check_updates(self):
for vm in self.qubes_app.domains:
if vm.klass == 'TemplateVM':
self.vms_in_table[vm.qid].update()
def on_domain_added(self, _, domain):
#needs to clear cache
self.qubes_app.domains.clear_cache()
qid = int(domain.split('/')[-1])
self.table.setSortingEnabled(False)
row_no = self.table.rowCount()
self.table.setRowCount(row_no + 1)
for vm in self.qubes_app.domains:
if vm.qid == qid:
vm_row = VmRowInTable(vm, row_no, self.table)
self.vms_in_table[vm.qid] = vm_row
self.table.setSortingEnabled(True)
return
# Never should reach here
raise RuntimeError('Added domain not found')
def on_domain_removed(self, _, domain):
#needs to clear cache
self.qubes_app.domains.clear_cache()
qid = int(domain.split('/')[-1])
# Find row and remove
try:
row_index = 0
vm_item = self.table.item(row_index, self.columns_indices["Name"])
while vm_item.qid != qid:
row_index += 1
vm_item = self.table.item(row_index,\
self.columns_indices["Name"])
except:
raise RuntimeError('Deleted domain not found')
self.table.removeRow(row_index)
del self.vms_in_table[qid]
def on_failed(self, _, domain):
qid = int(domain.split('/')[-1])
self.vms_in_table[qid].update()
if self.vms_in_table[qid].vm == self.get_selected_vm():
self.table_selection_changed()
def on_halted(self, _, domain):
qid = int(domain.split('/')[-1])
self.vms_in_table[qid].update()
if self.vms_in_table[qid].vm == self.get_selected_vm():
self.table_selection_changed()
# Check if is TemplatVM and update related AppVMs
starting_vm = self.vms_in_table[qid]
if starting_vm.vm.klass == 'TemplateVM':
for vm in starting_vm.vm.appvms:
if vm.klass == 'AppVM':
self.vms_in_table[vm.qid].update()
def on_halting(self, _, domain):
qid = int(domain.split('/')[-1])
self.vms_in_table[qid].update()
if self.vms_in_table[qid].vm == self.get_selected_vm():
self.table_selection_changed()
def on_started(self, _, domain):
qid = int(domain.split('/')[-1])
self.vms_in_table[qid].update()
if self.vms_in_table[qid].vm == self.get_selected_vm():
self.table_selection_changed()
def on_starting(self, _, domain):
qid = int(domain.split('/')[-1])
self.vms_in_table[qid].update()
if self.vms_in_table[qid].vm == self.get_selected_vm():
self.table_selection_changed()
# Check if is TemplatVM and update related AppVMs
starting_vm = self.vms_in_table[qid]
if starting_vm.vm.klass == 'TemplateVM':
for vm in starting_vm.vm.appvms:
if vm.klass == 'AppVM':
self.vms_in_table[vm.qid].update()
def load_manager_settings(self):
# visible columns
self.visible_columns_count = 0
@ -403,26 +517,16 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
def get_vms_list(self):
return [vm for vm in self.qubes_app.domains]
def update_single_row(self, vm):
# this fuction should be used to update a row that already exists
# to add a row, one needs to use the update_table function - the
# whole table needs to be redrawn (and sorted)
if vm in self.qubes_app.domains:
self.vms_in_table[vm.qid].update()
else:
self.update_table()
def fill_table(self):
# save current selection
row_index = self.table.currentRow()
selected_qid = -1
if row_index != -1:
vm_item = self.table.item(row_index, self.columns_indices["Name"])
if vm_item:
selected_qid = vm_item.qid
progress = QtGui.QProgressDialog(
self.tr(
"Loading Qube Manager..."), "", 0, 0)
progress.setWindowTitle(self.tr("Qube Manager"))
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
self.table.setSortingEnabled(False)
self.table.clearContents()
vms_list = self.get_vms_list()
vms_in_table = {}
@ -439,12 +543,9 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.vms_list = vms_list
self.vms_in_table = vms_in_table
if selected_qid in vms_in_table.keys():
self.table.setCurrentItem(
self.vms_in_table[selected_qid].name_widget)
self.table.setSortingEnabled(True)
self.showhide_vms()
progress.hide()
def showhide_vms(self):
if not self.search:
@ -467,19 +568,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
def action_search_triggered(self):
self.searchbox.setFocus()
def update_table(self):
# disabling the table speeds up the process of filling it
self.table.setDisabled(True)
self.fill_table()
self.table.setEnabled(True)
# TODO: instead of manually refreshing the entire table, use dbus events
# reapply sorting
if self.sort_by_column:
self.table.sortByColumn(self.columns_indices[self.sort_by_column])
self.table_selection_changed()
# noinspection PyPep8Naming
def sort_indicator_changed(self, column, order):
self.sort_by_column = [name for name in self.columns_indices if
@ -530,8 +618,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.action_set_keyboard_layout.setEnabled(
vm.qid != 0 and
vm.get_power_state() != "Paused" and vm.is_running())
self.update_single_row(vm)
else:
self.action_settings.setEnabled(False)
self.action_removevm.setEnabled(False)
@ -640,7 +726,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.tr("ERROR: {0}").format(
t_monitor.error_msg))
self.update_table()
@staticmethod
def do_remove_vm(vm, qubes_app, t_monitor):
@ -695,7 +780,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.tr("Exception while cloning:<br>{0}").format(
t_monitor.error_msg))
self.update_table()
@staticmethod
def do_clone_vm(src_vm, qubes_app, dst_name, t_monitor):
@ -716,6 +800,8 @@ 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!"),
@ -723,7 +809,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
return
self.start_vm(vm)
self.update_single_row(vm)
def start_vm(self, vm):
if vm.is_running():
@ -744,7 +829,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.tr("Error starting Qube!"),
self.tr("ERROR: {0}").format(t_monitor.error_msg))
self.update_single_row(vm)
@staticmethod
def do_start_vm(vm, t_monitor):
@ -769,7 +853,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
vm = self.get_selected_vm()
try:
vm.pause()
self.update_single_row(vm)
self.vms_in_table[vm.qid].update()
self.table_selection_changed()
except exc.QubesException as ex:
QtGui.QMessageBox.warning(
None,
@ -794,7 +879,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
if reply == QtGui.QMessageBox.Yes:
self.shutdown_vm(vm)
self.update_single_row(vm)
def shutdown_vm(self, vm, shutdown_time=vm_shutdown_timeout,
check_time=vm_restart_check_timeout, and_restart=False):
@ -835,8 +919,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
else:
self.start_vm(vm)
self.update_single_row(vm)
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_killvm_triggered')
def action_killvm_triggered(self):
@ -879,7 +961,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
settings_window = settings.VMSettingsWindow(
vm, self.qt_app, "basic")
settings_window.exec_()
self.update_single_row(vm)
self.vms_in_table[vm.qid].update()
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_appmenus_triggered')
@ -890,11 +972,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
vm, self.qt_app, "applications")
settings_window.exec_()
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_refresh_list_triggered')
def action_refresh_list_triggered(self):
self.qubes_app.domains.clear_cache()
self.update_table()
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_updatevm_triggered')
@ -940,7 +1017,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.tr("Error on Qube update!"),
self.tr("ERROR: {0}").format(t_monitor.error_msg))
self.update_single_row(vm)
@staticmethod
def do_update_vm(vm, t_monitor):
@ -1213,10 +1289,7 @@ def main():
qubes_app = Qubes()
manager_window = VmManagerWindow(qt_app, qubes_app)
manager_window.show()
timer = QtCore.QTimer()
timer.singleShot(1, manager_window.update_table)
qt_app.exec_()

View File

@ -18,10 +18,10 @@
#
# 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 datetime
from PyQt4 import QtGui # pylint: disable=import-error
from PyQt4 import QtCore # pylint: disable=import-error
import datetime
# pylint: disable=too-few-public-methods
power_order = QtCore.Qt.DescendingOrder
@ -110,7 +110,6 @@ class VmTypeWidget(VmIconWidget):
class VmLabelWidget(VmIconWidget):
class VmLabelItem(QtGui.QTableWidgetItem):
def __init__(self, value, vm):
super(VmLabelWidget.VmLabelItem, self).__init__()
@ -254,9 +253,9 @@ class VmInfoWidget(QtGui.QWidget):
self.table_item = self.VmInfoItem(self.upd_info.table_item, vm)
def update_vm_state(self, vm):
def update_vm_state(self):
self.on_icon.update()
self.upd_info.update_outdated(vm)
self.upd_info.update_outdated()
class VmTemplateItem(QtGui.QTableWidgetItem):
@ -264,18 +263,19 @@ class VmTemplateItem(QtGui.QTableWidgetItem):
super(VmTemplateItem, self).__init__()
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.setTextAlignment(QtCore.Qt.AlignVCenter)
self.update()
if getattr(vm, 'template', None) is not None:
self.setText(vm.template.name)
def update(self):
if getattr(self.vm, 'template', None) is not None:
self.setText(self.vm.template.name)
else:
font = QtGui.QFont()
font.setStyle(QtGui.QFont.StyleItalic)
self.setFont(font)
self.setTextColor(QtGui.QColor("gray"))
self.setText(vm.klass)
self.setTextAlignment(QtCore.Qt.AlignVCenter)
self.setText(self.vm.klass)
def __lt__(self, other):
if self.vm.qid == 0:
@ -292,13 +292,14 @@ class VmNetvmItem(QtGui.QTableWidgetItem):
super(VmNetvmItem, self).__init__()
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.setTextAlignment(QtCore.Qt.AlignVCenter)
self.update()
if getattr(vm, 'netvm', None) is None:
def update(self):
if getattr(self.vm, 'netvm', None) is None:
self.setText("n/a")
else:
self.setText(vm.netvm.name)
self.setTextAlignment(QtCore.Qt.AlignVCenter)
self.setText(self.vm.netvm.name)
def __lt__(self, other):
if self.vm.qid == 0:
@ -316,10 +317,13 @@ class VmInternalItem(QtGui.QTableWidgetItem):
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.internal = vm.features.get('internal', False)
self.update()
def update(self):
self.internal = self.vm.features.get('internal', False)
self.setText("Yes" if self.internal else "")
def __lt__(self, other):
if self.vm.qid == 0:
return True
@ -330,7 +334,6 @@ class VmInternalItem(QtGui.QTableWidgetItem):
# features man qvm-features
class VmUpdateInfoWidget(QtGui.QWidget):
class VmUpdateInfoItem(QtGui.QTableWidgetItem):
def __init__(self, value, vm):
super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
@ -367,34 +370,38 @@ class VmUpdateInfoWidget(QtGui.QWidget):
layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
self.setLayout(layout)
self.vm = vm
self.previous_outdated_state = None
self.previous_update_recommended = None
self.value = None
self.table_item = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
self.update_outdated(vm)
def update_outdated(self, vm):
self.update_outdated()
def update_outdated(self):
outdated_state = False
for vol in vm.volumes.values():
if vol.is_outdated():
outdated_state = "outdated"
break
if self.vm.is_running():
if hasattr(self.vm, 'template') and self.vm.template.is_running():
outdated_state = "to-be-outdated"
if not outdated_state and getattr(vm, 'template', None)\
and vm.template.is_running():
outdated_state = "to-be-outdated"
if outdated_state != self.previous_outdated_state:
self.update_status_widget(outdated_state)
self.previous_outdated_state = outdated_state
if not outdated_state:
for vol in self.vm.volumes.values():
if vol.is_outdated():
outdated_state = "outdated"
break
updates_available = vm.features.get('updates-available', False)
if updates_available != self.previous_update_recommended:
self.update_status_widget("update" if updates_available else None)
self.previous_update_recommended = updates_available
elif self.vm.klass == 'TemplateVM' and \
self.vm.features.get('updates-available', False):
outdated_state = 'update'
self.update_status_widget(outdated_state)
def update_status_widget(self, state):
if state == self.previous_outdated_state:
return
self.previous_outdated_state = state
self.value = state
self.table_item.set_value(state)
if state == "update":
@ -414,22 +421,22 @@ class VmUpdateInfoWidget(QtGui.QWidget):
"The Template must be stopped before changes from its "
"current session can be picked up by this qube.")
else:
label_text = ""
icon_path = None
tooltip_text = None
if hasattr(self, 'icon'):
self.icon.setVisible(False)
self.layout().removeWidget(self.icon)
del self.icon
if self.show_text:
self.label.setText(label_text)
else:
self.layout().removeWidget(self.icon)
self.icon.deleteLater()
if icon_path is not None:
self.icon = VmIconWidget(icon_path, True, 0.7)
self.icon.setToolTip(tooltip_text)
else:
self.icon = QtGui.QLabel(label_text)
self.layout().addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
self.layout().addWidget(self.icon,\
alignment=QtCore.Qt.AlignCenter)
self.icon.setVisible(True)
class VmSizeOnDiskItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
@ -465,6 +472,9 @@ class VmIPItem(QtGui.QTableWidgetItem):
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.update()
def update(self):
self.ip = getattr(self.vm, 'ip', None)
self.setText(self.ip if self.ip is not None else 'n/a')
@ -482,6 +492,9 @@ class VmIncludeInBackupsItem(QtGui.QTableWidgetItem):
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.update()
def update(self):
if getattr(self.vm, 'include_in_backups', None):
self.setText("Yes")
else:
@ -503,6 +516,9 @@ class VmLastBackupItem(QtGui.QTableWidgetItem):
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.update()
def update(self):
self.backup_timestamp = getattr(self.vm, 'backup_timestamp', None)
if self.backup_timestamp:

View File

@ -281,7 +281,6 @@
<addaction name="separator"/>
<addaction name="separator"/>
<addaction name="action_search"/>
<addaction name="action_refresh_list"/>
</widget>
<widget class="QMenu" name="menu_vm">
<property name="title">
@ -369,7 +368,6 @@
<addaction name="action_backup"/>
<addaction name="action_restore"/>
<addaction name="separator"/>
<addaction name="action_refresh_list"/>
</widget>
<action name="action_createvm">
<property name="icon">
@ -818,18 +816,6 @@
<string>Ctrl+F</string>
</property>
</action>
<action name="action_refresh_list">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/outdated.png</normaloff>:/outdated.png</iconset>
</property>
<property name="text">
<string>Refresh qube list</string>
</property>
<property name="toolTip">
<string>Refresh qube list</string>
</property>
</action>
<action name="action_exit">
<property name="text">
<string>&amp;Exit Qube Manager</string>

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1587</width>
<height>861</height>
<width>680</width>
<height>656</height>
</rect>
</property>
<property name="windowTitle">