diff --git a/usr/lib/linuxmint/mintUpdate/Classes.py b/usr/lib/linuxmint/mintUpdate/Classes.py index 53300c74..b2d1b1fe 100644 --- a/usr/lib/linuxmint/mintUpdate/Classes.py +++ b/usr/lib/linuxmint/mintUpdate/Classes.py @@ -5,13 +5,11 @@ import datetime import gettext -import html import json import os import subprocess import sys import time -import re import threading gettext.install("mintupdate", "/usr/share/locale") @@ -210,7 +208,7 @@ class UpdateTracker(): # Loads past updates from JSON file def __init__(self, settings, logger): - os.system("mkdir -p %s" % CONFIG_PATH) + os.makedirs(CONFIG_PATH, exist_ok=True) self.path = os.path.join(CONFIG_PATH, "updates.json") self.test_mode = os.getenv("MINTUPDATE_TEST") == "tracker-max-age" diff --git a/usr/lib/linuxmint/mintUpdate/mintUpdate.py b/usr/lib/linuxmint/mintUpdate/mintUpdate.py index f256f942..9055739d 100755 --- a/usr/lib/linuxmint/mintUpdate/mintUpdate.py +++ b/usr/lib/linuxmint/mintUpdate/mintUpdate.py @@ -34,6 +34,8 @@ # local imports import logger from kernelwindow import KernelWindow +import threading + from Classes import Update, PRIORITY_UPDATES, CONFIG_PATH, UpdateTracker, _idle, _async @@ -179,10 +181,11 @@ def __init__(self): self.updates_inhibited = False self.reboot_required = False self.refreshing = False - self.refreshing_apt = False - self.refreshing_flatpak = False - self.refreshing_cinnamon = False - self.auto_refresh_is_alive = False + self.refresh_threads = [] + self.apt_cache_done = threading.Event() + self.apt_cache_done.set() + self.auto_refresh_wakeup = threading.Event() + self.initial_refresh_done = False self.hidden = True # whether the window is hidden or not self.packages = [] # packages selected for update self.flatpaks = [] # flatpaks selected for update @@ -438,11 +441,11 @@ def __init__(self): newVersionColumnMenuItem.connect("toggled", self.setVisibleColumn, column_new_version, "show-new-version-column") visibleColumnsMenu.append(newVersionColumnMenuItem) - sizeColumnMenuItem = Gtk.CheckMenuItem(label=_("Origin")) - sizeColumnMenuItem.set_active(self.settings.get_boolean("show-origin-column")) + originColumnMenuItem = Gtk.CheckMenuItem(label=_("Origin")) + originColumnMenuItem.set_active(self.settings.get_boolean("show-origin-column")) column_origin.set_visible(self.settings.get_boolean("show-origin-column")) - sizeColumnMenuItem.connect("toggled", self.setVisibleColumn, column_origin, "show-origin-column") - visibleColumnsMenu.append(sizeColumnMenuItem) + originColumnMenuItem.connect("toggled", self.setVisibleColumn, column_origin, "show-origin-column") + visibleColumnsMenu.append(originColumnMenuItem) sizeColumnMenuItem = Gtk.CheckMenuItem(label=_("Size")) sizeColumnMenuItem.set_active(self.settings.get_boolean("show-size-column")) @@ -532,7 +535,6 @@ def __init__(self): self.ui_notebook_details.set_current_page(0) - self.refresh_schedule_enabled = self.settings.get_boolean("refresh-schedule-enabled") self.start_auto_refresh() Gtk.main() @@ -552,6 +554,12 @@ def _on_settings_changed(self, settings, key, data=None): self.app_restart_required = settings.get_boolean("show-cinnamon-updates") != self.show_cinnamon_enabled or \ settings.get_boolean("show-flatpak-updates") != self.show_flatpak_enabled + if key in ("refresh-minutes", "refresh-hours", "refresh-days", + "autorefresh-minutes", "autorefresh-hours", "autorefresh-days", + "refresh-schedule-enabled"): + self.auto_refresh_wakeup.set() + self.start_auto_refresh() + ######### EVENT HANDLERS ######### @@ -764,7 +772,6 @@ def set_refresh_mode(self, enabled): # Make sure we're never stuck on the status_refreshing page: if self.ui_stack.get_visible_child_name() == "refresh_page": self.ui_stack.set_visible_child_name("updates_page") - #self.ui_paned.set_position(self.ui_paned.get_position()) self.ui_toolbar.set_sensitive(True) self.ui_menubar.set_sensitive(True) self.set_window_busy(enabled) @@ -1113,15 +1120,16 @@ def retrieve_changelog(self, update): @_async def start_auto_refresh(self): - self.auto_refresh_is_alive = True + self.auto_refresh_wakeup.clear() minute = 60 hour = 60 * minute day = 24 * hour - initial_refresh = True - settings_prefix = "" - refresh_type = "initial" + initial_refresh = not self.initial_refresh_done + retry_soon = False + settings_prefix = "" if initial_refresh else "auto" + refresh_type = "initial" if initial_refresh else "auto" - while self.refresh_schedule_enabled: + while self.settings.get_boolean("refresh-schedule-enabled"): try: schedule = { "minutes": self.settings.get_int("%srefresh-minutes" % settings_prefix), @@ -1130,59 +1138,43 @@ def start_auto_refresh(self): } timetosleep = schedule["minutes"] * minute + schedule["hours"] * hour + schedule["days"] * day + self.logger.write(f"auto-refresh iteration: refresh_type={refresh_type}, initial_refresh={initial_refresh}, retry_soon={retry_soon}, configured timetosleep={timetosleep}s") + if not timetosleep: - time.sleep(60) # sleep 1 minute, don't mind the config we don't want an infinite loop to go nuts :) + # sleep 1 minute, don't mind the config we don't want an infinite loop to go nuts :) + if self.auto_refresh_wakeup.wait(timeout=60): + return else: - now = int(time.time()) - if not initial_refresh: - refresh_last_run = self.settings.get_int("refresh-last-run") - if not refresh_last_run or refresh_last_run > now: - refresh_last_run = now - self.settings.set_int("refresh-last-run", now) - time_since_last_refresh = now - refresh_last_run - if time_since_last_refresh > 0: - timetosleep = timetosleep - time_since_last_refresh - # always wait at least 1 minute to be on the safe side - if timetosleep < 60: - timetosleep = 60 + if retry_soon: + self.logger.write("retry_soon set from previous iteration; clamping sleep to 60s") + timetosleep = 60 + retry_soon = False schedule["days"] = int(timetosleep / day) schedule["hours"] = int((timetosleep - schedule["days"] * day) / hour) schedule["minutes"] = int((timetosleep - schedule["days"] * day - schedule["hours"] * hour) / minute) self.logger.write("%s refresh will happen in %d day(s), %d hour(s) and %d minute(s)" % (refresh_type.capitalize(), schedule["days"], schedule["hours"], schedule["minutes"])) - time.sleep(timetosleep) - if not self.refresh_schedule_enabled: - self.logger.write(f"Auto-refresh disabled in preferences; cancelling {refresh_type} refresh") - self.uninhibit_pm() + if self.auto_refresh_wakeup.wait(timeout=timetosleep): + self.logger.write(f"woke early during {refresh_type} refresh sleep") return if self.hidden: self.logger.write(f"Update Manager is in tray mode; performing {refresh_type} refresh") self.refresh(True) - # FIXME: self.refresh() is an _idle function, and we're on a thread - we will continue - # and loop before self.refreshing is set. Force a brief dwell to allow the refresh() call - # to get ahead of us and set self.refreshing and update 'refresh-last-run', otherwise we'll - # get a double-refresh 1 minute apart every time. - time.sleep(0.5) - while self.refreshing: - time.sleep(5) else: - if initial_refresh: - self.logger.write(f"Update Manager window is open; skipping {refresh_type} refresh") - else: - self.logger.write(f"Update Manager window is open; delaying {refresh_type} refresh by 60s") - time.sleep(60) + action = "skipping" if initial_refresh else "delaying" + self.logger.write(f"Update Manager window is open; {action} {refresh_type} refresh will retry shortly") + retry_soon = True except Exception as e: print (e) self.logger.write_error("Exception occurred during %s refresh: %s" % (refresh_type, str(sys.exc_info()[0]))) if initial_refresh: initial_refresh = False + self.initial_refresh_done = True settings_prefix = "auto" refresh_type = "auto" - else: - self.logger.write("Auto-refresh disabled in preferences, automatic refresh thread stopped") - self.auto_refresh_is_alive = False + self.logger.write("Auto-refresh disabled in preferences, automatic refresh thread stopped") def switch_page(self, notebook, page, page_num): @@ -1561,7 +1553,6 @@ def open_preferences(self, widget, show_automation=False): section = page.add_section(_("Auto-refresh")) switch = GSettingsSwitch(_("Refresh the list of updates automatically"), "com.linuxmint.updates", "refresh-schedule-enabled") - switch.content_widget.connect("notify::active", self.auto_refresh_toggled) section.add_row(switch) grid = Gtk.Grid() @@ -1645,7 +1636,6 @@ def open_preferences(self, widget, show_automation=False): page = SettingsPage() box.pack_start(page, True, True, 0) - # if False: if os.path.exists("/usr/bin/cinnamon") or os.path.exists("/usr/bin/flatpak"): section = page.add_section(_("Update types"), _("In addition to system packages, check for:")) @@ -1725,11 +1715,6 @@ def export_blacklist(self, widget): f.write("\n".join(blacklist) + "\n") subprocess.run(["pkexec", "/usr/bin/mintupdate-automation", "blacklist", "enable"]) - def auto_refresh_toggled(self, widget, param): - self.refresh_schedule_enabled = widget.get_active() - if self.refresh_schedule_enabled and not self.auto_refresh_is_alive: - self.start_auto_refresh() - def set_auto_upgrade(self, widget, param): exists = os.path.isfile(AUTOMATIONS["upgrade"][2]) action = None @@ -2112,25 +2097,21 @@ def refresh(self, refresh_cache): # refresh_updates() waits for them to finish self.logger.write("Refreshing cache") - # APT - self.settings.set_int("refresh-last-run", int(time.time())) - self.refreshing_apt = True + self.refresh_threads = [] + if self.hidden: - self.refresh_apt_cache_externally() + self.refresh_threads.append(self.refresh_apt_cache_externally()) else: + self.apt_cache_done.clear() client = aptkit.simpleclient.SimpleAPTClient(self.ui_window) client.set_finished_callback(self.on_cache_updated) client.update_cache() - # Cinnamon if CINNAMON_SUPPORT: - self.refreshing_cinnamon = True - self.refresh_cinnamon_cache() + self.refresh_threads.append(self.refresh_cinnamon_cache()) - # Flatpak if FLATPAK_SUPPORT: - self.refreshing_flatpak = True - self.refresh_flatpak_cache() + self.refresh_threads.append(self.refresh_flatpak_cache()) self.refresh_updates() @@ -2154,8 +2135,6 @@ def refresh_apt_cache_externally(self): subprocess.run(refresh_command) except: print("Exception while calling mint-refresh-cache") - finally: - self.refreshing_apt = False @_async def refresh_cinnamon_cache(self): @@ -2166,16 +2145,14 @@ def refresh_cinnamon_cache(self): except: self.logger.write_error("Something went wrong fetching Cinnamon %ss: %s" % (spice_type, str(sys.exc_info()[0]))) print("-- Exception occurred fetching Cinnamon %ss:\n%s" % (spice_type, traceback.format_exc())) - self.refreshing_cinnamon = False @_async def refresh_flatpak_cache(self): self.logger.write("Refreshing cache for Flatpak updates") self.flatpak_updater.refresh() - self.refreshing_flatpak = False def on_cache_updated(self, transaction=None, exit_state=None): - self.refreshing_apt = False + self.apt_cache_done.set() # ---------------- Test Mode ------------------------------------------# @@ -2256,8 +2233,9 @@ def check_apt_in_external_process(self, queue): @_async def refresh_updates(self): # Wait for all the caches to be refreshed - while (self.refreshing_apt or self.refreshing_flatpak or self.refreshing_cinnamon): - time.sleep(1) + for t in self.refresh_threads: + t.join() + self.apt_cache_done.wait() # Check presence of Mint layer if self.test_mode == "layer-error" or (not self.check_policy()): diff --git a/usr/share/glib-2.0/schemas/com.linuxmint.updates.gschema.xml b/usr/share/glib-2.0/schemas/com.linuxmint.updates.gschema.xml index a766cde2..3cefa2f8 100644 --- a/usr/share/glib-2.0/schemas/com.linuxmint.updates.gschema.xml +++ b/usr/share/glib-2.0/schemas/com.linuxmint.updates.gschema.xml @@ -31,11 +31,6 @@ - - 0 - - - 0