fix: updater survives Settings saves + cache unsniffable MLGs (v1.6.1)

Two reliability fixes for the Series 3 tray app:

1. Auto-updater no longer dies on a Settings save. _restart_watcher
   set+replaced the shared stop_event that the updater loop also keyed
   off, so saving settings could kill the updater thread (race). Give
   the updater its own app-lifetime event (_app_stop). Same fix as
   thor-watcher 0.4.1.

2. Unsniffable .MLG files are now cached, so each is sniffed + logged
   once per session instead of every 5-min scan. A long-lived autocall
   folder with a few hundred unidentifiable files was flooding the log
   (~600K lines in 10 days) and re-reading every header each scan.

Adds test_series3_tray.py (4 lifecycle tests) and test_scan_latest.py
(2 scan tests). Full suite 50 passing. Bump to v1.6.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 20:08:03 +00:00
parent e6e4c94f19
commit aa341c7342
8 changed files with 170 additions and 15 deletions
+7 -4
View File
@@ -1,5 +1,5 @@
"""
Series 3 Watcher — System Tray Launcher v1.6.0
Series 3 Watcher — System Tray Launcher v1.6.1
Requires: pystray, Pillow, tkinter (stdlib)
Run with: pythonw series3_tray.py (no console window)
@@ -335,7 +335,8 @@ def _show_cancel_message():
class WatcherTray:
def __init__(self):
self.state = {}
self.stop_event = threading.Event()
self.stop_event = threading.Event() # watcher lifecycle; replaced on restart
self._app_stop = threading.Event() # app lifetime; only set on exit/update
self._watcher_thread = None
self._icon = None
# Lock guards _rebuild_menu calls from the updater thread
@@ -394,6 +395,7 @@ class WatcherTray:
subprocess.Popen(["explorer", HERE])
def _exit(self, icon, item):
self._app_stop.set()
self.stop_event.set()
icon.stop()
@@ -459,7 +461,7 @@ class WatcherTray:
last_status = None
update_check_counter = 0 # check for updates every ~5 min (30 * 10s ticks)
while not self.stop_event.is_set():
while not self._app_stop.is_set():
icon_status = self._tray_status()
if self._icon is not None:
@@ -487,7 +489,7 @@ class WatcherTray:
self._do_update(url)
return # exit loop; swap bat will relaunch
self.stop_event.wait(timeout=10)
self._app_stop.wait(timeout=10)
def _do_update(self, download_url=None):
"""Notify tray icon then apply update. If url is None, fetch it first."""
@@ -502,6 +504,7 @@ class WatcherTray:
success = apply_update(download_url)
if success:
self._app_stop.set()
self.stop_event.set()
if self._icon is not None:
self._icon.stop()