diff --git a/qui/updater/intro_page.py b/qui/updater/intro_page.py index 91cf2e45..c1fce792 100644 --- a/qui/updater/intro_page.py +++ b/qui/updater/intro_page.py @@ -43,7 +43,7 @@ class IntroPage: """ First content page of updater. - Show the list of updatable vms with an update info. + Show the list of updatable vms with update info. """ def __init__(self, builder, log, next_button): @@ -86,7 +86,7 @@ def __init__(self, builder, log, next_button): )) def populate_vm_list(self, qapp, settings): - """Adds to list any updatable vms with an update info.""" + """Adds to list any updatable vms with update info.""" self.log.debug("Populate update list") self.list_store = ListWrapper( UpdateRowWrapper, self.vm_list.get_model()) @@ -113,16 +113,14 @@ def refresh_update_list(self, update_if_stale): if not self.active: return - output = subprocess.check_output( - ['qubes-vm-update', '--dry-run', - '--update-if-stale', str(update_if_stale)]) + cmd = ['qubes-vm-update', '--dry-run', + '--update-if-stale', str(update_if_stale)] - to_update = [ - vm_name.strip() for vm_name - in output.decode().split("\n", maxsplit=1)[0] - .split(":", maxsplit=1)[1].split(",")] + to_update = self._get_stale_qubes(cmd) for row in self.list_store: + if row.vm.name == 'dom0': + continue row.updates_available = bool(row.vm.name in to_update) def get_vms_to_update(self) -> ListWrapper: @@ -162,7 +160,7 @@ def on_header_toggled(self, _emitter): Cycle between selection of: <1> vms with `updates_available` (YES) <2> <1> + vms no checked for updates for a while (YES and MAYBE) - <3> all vms (YES , MAYBE and NO) + <3> all vms (YES, MAYBE and NO) <4> no vm. (nothing) If the user has selected any vms that do not match the defined states, @@ -176,7 +174,7 @@ def select_rows(self): row.selected = row.updates_available \ in self.head_checkbox.allowed - def select_rows_ignoring_conditions(self, cliargs): + def select_rows_ignoring_conditions(self, cliargs, dom0): cmd = ['qubes-vm-update', '--dry-run'] args = [a for a in dir(cliargs) if not a.startswith("_")] @@ -192,31 +190,48 @@ def select_rows_ignoring_conditions(self, cliargs): if not vms_without_dom0: continue value = ",".join(vms_without_dom0) - cmd.extend((f"--{arg.replace('_', '-')}", str(value))) + cmd.append(f"--{arg.replace('_', '-')}") + if isinstance(value, str): + cmd.append(value) - if not cmd[2:]: - to_update = set() - else: + to_update = set() + if cmd[2:]: + to_update = self._get_stale_qubes(cmd) + + to_update = self._handle_cli_dom0(dom0, to_update, cliargs) + + for row in self.list_store: + row.selected = row.name in to_update + + def _get_stale_qubes(self, cmd): + try: self.log.debug("Run command %s", " ".join(cmd)) output = subprocess.check_output(cmd) self.log.debug("Command returns: %s", output.decode()) - to_update = { + return { vm_name.strip() for vm_name in output.decode().split("\n", maxsplit=1)[0] .split(":", maxsplit=1)[1].split(",") } + except subprocess.CalledProcessError as err: + if err.returncode != 100: + raise err + return set() - # handle dom0 + @staticmethod + def _handle_cli_dom0(dom0, to_update, cliargs): + if not cliargs.targets and not cliargs.all: + if bool(dom0.features.get( + 'updates-available', False)): + to_update.add('dom0') if cliargs.dom0 or cliargs.all: to_update.add("dom0") if cliargs.targets and "dom0" in cliargs.targets.split(","): to_update.add("dom0") if cliargs.skip and "dom0" in cliargs.skip.split(","): to_update = to_update.difference({"dom0"}) - - for row in self.list_store: - row.selected = row.name in to_update + return to_update class UpdateRowWrapper(RowWrapper): diff --git a/qui/updater/updater.py b/qui/updater/updater.py index 845bdebd..fbeb596c 100644 --- a/qui/updater/updater.py +++ b/qui/updater/updater.py @@ -41,6 +41,7 @@ def __init__(self, qapp, cliargs): ) self.qapp = qapp self.primary = False + self.do_nothing = False self.connect("activate", self.do_activate) self.cliargs = cliargs @@ -55,11 +56,22 @@ def __init__(self, qapp, cliargs): def do_activate(self, *_args, **_kwargs): if not self.primary: + self.log.debug("Primary activation") self.perform_setup() self.primary = True self.hold() else: - self.main_window.present() + self.log.debug("Secondary activation") + if self.do_nothing: + show_dialog_with_icon( + None, l("Quit"), + l("Nothing to do."), + buttons=RESPONSES_OK, + icon_name="check_yes" + ) + self.window_close() + else: + self.main_window.present() def perform_setup(self, *_args, **_kwargs): self.log.debug("Setup") @@ -157,15 +169,9 @@ def cell_data_func(_column, cell, model, it, data): if skip_intro_if_args(self.cliargs): self.log.info("Skipping intro page.") self.intro_page.select_rows_ignoring_conditions( - cliargs=self.cliargs) + cliargs=self.cliargs, dom0=self.qapp.domains['dom0']) if len(self.intro_page.get_vms_to_update()) == 0: - show_dialog_with_icon( - None, l("Quit"), - l("Nothing to do."), - buttons=RESPONSES_OK, - icon_name="qubes-info" - ) - self.main_window.close() + self.do_nothing = True return self.next_clicked(None, skip_intro=True) else: @@ -174,6 +180,7 @@ def cell_data_func(_column, cell, model, it, data): width = self.intro_page.vm_list.get_preferred_width().natural_width self.main_window.resize(width + 50, int(width * 1.2)) self.main_window.set_position(Gtk.WindowPosition.CENTER_ALWAYS) + # return 0 def open_settings_window(self, _emitter): self.settings.show() @@ -268,8 +275,8 @@ def parse_args(args): group.add_argument('--targets', action='store', help='Comma separated list of VMs to target') group.add_argument('--all', action='store_true', - help='Target all non-disposable VMs (TemplateVMs and ' - 'AppVMs)') + help='Target all updatable VMs (AdminVM, ' + 'TemplateVMs and StandaloneVMs)') group.add_argument('--update-if-stale', action='store', help='Target all TemplateVMs with known updates or for ' 'which last update check was more than N days '