diff --git a/qui/tray/disk_space.py b/qui/tray/disk_space.py index 713a47ac..ff890a2b 100644 --- a/qui/tray/disk_space.py +++ b/qui/tray/disk_space.py @@ -1,12 +1,15 @@ # pylint: disable=wrong-import-position,import-error import sys import subprocess +from typing import List + import gi gi.require_version('Gtk', '3.0') # isort:skip from gi.repository import Gtk, GObject, Gio, GLib # isort:skip from qubesadmin import Qubes from qubesadmin.utils import size_to_human from qubesadmin import exc +from qubesadmin.storage import Pool import gettext t = gettext.translation("desktop-linux-manager", fallback=True) @@ -143,11 +146,43 @@ def __init__(self, vm): self.show_all() +class PoolWrapper: + """Wrapper for pools, to manage various exceptions thrown by properties.""" + def __init__(self, pool: Pool): + self.pool = pool + self.has_error = False + self.size = self._get_attribute('size', None) + self.usage = self._get_attribute('usage', None) + self.config = self._get_attribute('config', {}) + self.usage_details = self._get_attribute('usage_details', {}) + self.name = self._get_attribute('name', 'ERROR: pool name unknown') + + self.metadata_size = self.usage_details.get('metadata_size', None) + self.metadata_usage = self.usage_details.get('metadata_usage', None) + + if self.size and self.usage: + self.usage_perc = self.usage / self.size + else: + self.usage_perc = 0 + + if self.metadata_size and self.metadata_usage: + self.metadata_perc = self.metadata_usage / self.metadata_size + else: + self.metadata_perc = 0 + + def _get_attribute(self, attribute_name, default): + try: + return getattr(self.pool, attribute_name, default) + except exc.StoragePoolException: + self.has_error = True + return default + + class PoolUsageData: def __init__(self, qubes_app): self.qubes_app = qubes_app - self.pools = [] + self.pools: List[PoolWrapper] = [] self.total_size = 0 self.used_size = 0 self.warning_message = [] @@ -160,31 +195,29 @@ def __populate_pools(self): except exc.QubesDaemonAccessError: pools = [] for pool in pools: - self.pools.append(pool) - if not getattr(pool, 'size', None) or \ - 'included_in' in getattr(pool, 'config', {}): + wrapped_pool = PoolWrapper(pool) + self.pools.append(wrapped_pool) + + if wrapped_pool.has_error: continue - self.total_size += getattr(pool, 'size', 0) - self.used_size += getattr(pool, 'usage', 0) - try: - if pool.usage/pool.size >= URGENT_WARN_LEVEL: - self.warning_message.append( - _("\n{:.1%} space left in pool {}").format( - 1-pool.usage/pool.size, pool.name)) - except (ValueError, exc.QubesDaemonAccessError): - pass - try: - if pool.usage_details.get('metadata_size', None): - metadata_usage = pool.usage_details['metadata_usage'] / \ - pool.usage_details['metadata_size'] - if metadata_usage >= URGENT_WARN_LEVEL: - # pylint: disable=consider-using-f-string - self.warning_message.append(_( - "\nMetadata space for pool {} is running out. " - "Current usage: {.1%}").format( - pool.name, metadata_usage)) - except (exc.QubesPropertyAccessError, AttributeError): - pass + + if not wrapped_pool.size or not wrapped_pool.usage or \ + 'included_in' in wrapped_pool.config: + continue + self.total_size += wrapped_pool.size + self.used_size += wrapped_pool.usage + + if wrapped_pool.usage_perc >= URGENT_WARN_LEVEL: + self.warning_message.append( + _("\n{:.1%} space left in pool {}").format( + 1-wrapped_pool.usage_perc, wrapped_pool.name)) + + if wrapped_pool.metadata_perc >= URGENT_WARN_LEVEL: + # pylint: disable=consider-using-f-string + self.warning_message.append(_( + "\nMetadata space for pool {} is running out. " + "Current usage: {:.0%}").format( + wrapped_pool.name, wrapped_pool.metadata_perc)) def get_pools_widgets(self): for p in self.pools: @@ -199,76 +232,77 @@ def get_usage(self): return 0 @staticmethod - def __create_box(pool): + def __create_box(pool: PoolWrapper): name_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) percentage_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) usage_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) pool_name = Gtk.Label(xalign=0) - if getattr(pool, 'size', None) and \ - 'included_in' not in getattr(pool, 'config', None): - # pool with detailed usage data - has_metadata = 'metadata_size' in getattr( - pool, 'usage_details', {}) and \ - pool.usage_details['metadata_size'] + if pool.has_error: + # Pool with errors + formatted_name = \ + f'{pool.name}{pool.name}' + else: + # pool without data or included in another pool + formatted_name = f'{pool.name}' - pool_name.set_markup(f'{pool.name}') + pool_name.set_markup(formatted_name) + pool_name.set_margin_left(20) - data_name = Gtk.Label(xalign=0) - data_name.set_markup("data") - data_name.set_margin_left(40) + name_box.pack_start(pool_name, True, True, 0) - name_box.pack_start(pool_name, True, True, 0) - name_box.pack_start(data_name, True, True, 0) + if not pool.size or 'included_in' in pool.config: + return name_box, percentage_box, usage_box - if has_metadata: - metadata_name = Gtk.Label(xalign=0) - metadata_name.set_markup("metadata") - metadata_name.set_margin_left(40) + if pool.has_error: + error_desc = Gtk.Label(xalign=0) + error_desc.set_markup("Error accessing pool data") + error_desc.set_margin_left(40) + name_box.pack_start(error_desc, True, True, 0) + return name_box, percentage_box, usage_box - name_box.pack_start(metadata_name, True, True, 0) + data_name = Gtk.Label(xalign=0) + data_name.set_markup("data") + data_name.set_margin_left(40) - try: - percentage = pool.usage/pool.size - except (exc.QubesPropertyAccessError, ValueError): - percentage = 0 - - percentage_use = Gtk.Label() - percentage_use.set_markup(colored_percentage(percentage)) - percentage_use.set_justify(Gtk.Justification.RIGHT) - - # empty label to guarantee proper alignment - percentage_box.pack_start(Gtk.Label(), True, True, 0) - percentage_box.pack_start(percentage_use, True, True, 0) - - if has_metadata: - metadata_usage = pool.usage_details['metadata_usage'] / \ - pool.usage_details['metadata_size'] - metadata_label = Gtk.Label() - metadata_label.set_markup(colored_percentage( - metadata_usage)) - percentage_box.pack_start(metadata_label, True, True, 0) - - numeric_label = Gtk.Label() - numeric_label.set_markup( - '' - f'{size_to_human(getattr(pool, "usage", 0))}/' - f'{size_to_human(getattr(pool, "size", 0))}') - numeric_label.set_justify(Gtk.Justification.RIGHT) - - # pack with empty labels to guarantee proper alignment - usage_box.pack_start(Gtk.Label(), True, True, 0) - usage_box.pack_start(numeric_label, True, True, 0) - usage_box.pack_start(Gtk.Label(), True, True, 0) + name_box.pack_start(data_name, True, True, 0) - else: - # pool that is included in other pools and/or has no usage data - pool_name.set_markup( - f'{pool.name}') - name_box.pack_start(pool_name, True, True, 0) + if pool.metadata_perc: + metadata_name = Gtk.Label(xalign=0) + metadata_name.set_markup("metadata") + metadata_name.set_margin_left(40) - pool_name.set_margin_left(20) + name_box.pack_start(metadata_name, True, True, 0) + + percentage_use = Gtk.Label() + percentage_use.set_markup(colored_percentage(pool.usage_perc)) + percentage_use.set_justify(Gtk.Justification.RIGHT) + + # empty label to guarantee proper alignment + percentage_box.pack_start(Gtk.Label(), True, True, 0) + percentage_box.pack_start(percentage_use, True, True, 0) + + if pool.metadata_perc: + metadata_label = Gtk.Label() + metadata_label.set_markup(colored_percentage( + pool.metadata_perc)) + percentage_box.pack_start(metadata_label, True, True, 0) + + numeric_label = Gtk.Label() + numeric_label.set_markup( + '' + f'{size_to_human(pool.usage or 0)}/' + f'{size_to_human(pool.size or 0)}') + numeric_label.set_justify(Gtk.Justification.RIGHT) + + # pack with empty labels to guarantee proper alignment + usage_box.pack_start(Gtk.Label(), True, True, 0) + usage_box.pack_start(numeric_label, True, True, 0) + usage_box.pack_start(Gtk.Label(), True, True, 0) return name_box, percentage_box, usage_box