Files
thor-watcher/CHANGELOG.md
T
serversdown a92afd64cd fix(updater): keep auto-updater alive across watcher restarts (v0.4.1)
Saving the Settings dialog calls _restart_watcher, which set+replaced the
shared stop_event the updater loop also keyed off — so a settings save
silently killed the auto-updater thread (race: survived one save, died on
the next). Heartbeats kept flowing but the agent stopped checking for /
applying updates until relaunch.

Give the updater its own app-lifetime event (_app_stop), set only on
Exit / when applying an update and untouched by watcher restarts. Adds
test_thor_tray.py (4 lifecycle tests). Bump to v0.4.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 18:01:58 +00:00

108 lines
7.9 KiB
Markdown

# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.4.1] - 2026-06-25
### Fixed
- **Auto-updater no longer dies when settings are saved.** Saving the Settings dialog restarts the watcher thread via `_restart_watcher`, which sets and then replaces the shared `stop_event`. Because the updater loop keyed off that *same* event, saving settings would terminate the auto-updater thread (a race — it could survive one save and die on the next). The watcher kept running and reporting heartbeats normally, but the agent silently stopped checking for / applying updates until the tray app was relaunched. The updater now keys off its own app-lifetime event (`_app_stop`) that watcher restarts never touch; it stops only on Exit or when applying an update.
- Added `test_thor_tray.py` covering the tray thread lifecycle (updater survives a watcher restart; stops on app exit; `_exit`/`_do_update` signal the lifetime event).
## [0.4.0] - 2026-06-25
### Added — Mirror (dual-send)
- **Optional best-effort mirror server.** Each heartbeat and each forwarded event can now be sent to a *second* ("mirror") server in addition to the primary, so a standby box (e.g. the office NAS) stays a continuous replica during a migration — making the eventual cutover a non-event. **Default off** — blank `mirror_api_url`/`mirror_sfm_url` means existing installs are unchanged after auto-update.
- **Heartbeat mirror (best-effort).** After the primary heartbeat POST, the same payload is fired at `mirror_api_url` when set. Bounded by the existing API timeout and fully wrapped — it can never delay or fail the primary heartbeat; its result is ignored except for a debug log line.
- **Event mirror (reliable, isolated).** Events are re-forwarded to `mirror_sfm_url` using a *separate* sha256 state file (`mirror_sfm_state_file`, blank → `<log_dir>/thor_forwarded_mirror.json`), so the mirror tracks its own delivery independently of the primary. A quick reachability probe (~3 s) skips the mirror pass when the mirror server is down, so a dead mirror never stalls the loop on per-event timeouts; skipped events stay pending in the mirror state and deliver when the mirror returns — **no data loss**.
- **Isolation invariant (the one rule).** Nothing on the mirror path can delay or fail the primary: its own state file, its own try/except, the reachability guard + bounded timeouts, all exceptions swallowed-and-logged. The primary path stays exactly as reliable as before.
- **Mirror tab in the Settings dialog** with `Mirror API URL` + `Mirror SFM URL` fields (blank = off). The mirror rides along with the primary — there is no separate enable flag (heartbeat-mirror active iff the primary heartbeat is on *and* `mirror_api_url` is set; event-mirror active iff primary forwarding is on *and* `mirror_sfm_url` is set).
- New unit tests in `test_event_forwarder.py` covering mirror reachability, the down-mirror skip, separate-state idempotency, and the isolation invariant (a mirror failure leaves the primary's result + state untouched).
### Configuration
New `config.json` keys (all default-empty = off; existing 0.3.x deployments don't change behaviour on auto-update):
| Key | Default | Notes |
|---|---|---|
| `mirror_api_url` | `""` | Second heartbeat destination — same URL form as `api_url` (blank = off) |
| `mirror_sfm_url` | `""` | Second SFM base URL, e.g. `http://10.0.0.x:8200` (blank = off) |
| `mirror_sfm_state_file` | `""` | Override the mirror's forwarded-sha256 state file. Blank → `<log_dir>\thor_forwarded_mirror.json` |
### Changed
- Bumped `VERSION` to `0.4.0`
> **Operational note (migration seeding).** To make the mirror re-deliver only the gap since a one-time replica snapshot, copy the primary state file to the mirror state file and drop entries whose `forwarded_at` is after the snapshot. SFM-side dedup covers any overlap. See `docs/mirror-dual-send-design.md`.
## [0.3.1] - 2026-05-20
### Hot fix
- Improved update logging
- Improved update settings
## [0.3.0] - 2026-05-19
### Added
- `event_forwarder.py` — forwards `.IDFH` (histogram) and `.IDFW` (waveform) event files plus their `TXT/<basename>.txt` sidecars to a seismo-relay SFM server's new `/db/import/idf_file` endpoint
- Sha256-keyed `thor_forwarded.json` state file for idempotency across restarts and re-scans (default path: `<log_dir>/thor_forwarded.json`)
- "SFM Forward" tab in Settings dialog: enable/URL/Test, forward interval, quiescence, missing-report grace, HTTP timeout, max forwards per pass, max event age, state file picker
- Forwarder status line in tray menu: `SFM OK | N fwd, M err | last 30s ago`
- Tray icon goes amber when the SFM forwarder is failing but the API heartbeat is still healthy
- Re-pair logic: events forwarded without their TXT are re-forwarded once the sidecar appears so the relay can refresh DB rows with device-authoritative PPV/ZCFreq/peak values
- `event_forwarder.py --seed-state` CLI for skipping historical backfill on a first deploy
- Version badge: `Thor Watcher vX.Y.Z` shown at the top of the tray menu and in the Settings dialog title bar — operators no longer have to crack open the .exe properties to tell which version is running
### Changed
- Bumped `VERSION` to `0.3.0`
- Settings dialog tab order: Connection / Paths / Scanning / Logging / **SFM Forward** / Updates
## [0.2.0] - 2026-03-20
### Added
- `thor_tray.py` — system tray launcher with status icon (green/amber/red/grey), Settings and Open Log Folder menu items
- `thor_settings_dialog.py` — Tkinter settings dialog with first-run wizard; tabs for Connection, Paths, Scanning, Logging, Updates
- Hardened auto-updater: three-layer download validation (100 KB floor, 50% relative size floor, MZ magic bytes), safer swap bat with 5-retry cap and `:fail` exit, `.exe.old` backup
- Configurable update source: `update_source` (gitea / url / disabled), `update_url` for custom server
- Remote push support: `update_available` flag in API heartbeat response triggers update regardless of `update_source` setting
- `build.bat` — PyInstaller build script; outputs versioned exe for Gitea and plain copy for Inno Setup
- `installer.iss` — Inno Setup installer script with startup shortcut
- `_update_log()` helper writes timestamped `[updater]` lines to the watcher log
- `log_tail` included in heartbeat payload (last 25 lines) for terra-view display
- `run_watcher(state, stop_event)` pattern in `series4_ingest.py` for background thread use from tray
### Changed
- `series4_ingest.py` refactored into tray-friendly background thread module; `main()` retained for standalone use
- Config key `sfm_endpoint` renamed to `api_url` for consistency with series3-watcher
- Heartbeat payload now uses `source_id`, `source_type`, `version` fields matching terra-view WatcherAgent model
- AppData folder: `ThorWatcher` (was not previously defined)
## [0.1.1] - 2025-12-08
### Changed
- Moved configuration from hard-coded values to external `config.json` file
- Configuration now loads from JSON file with graceful fallback to defaults
- Config file is loaded relative to script location for portability
### Added
- `config.json` - External configuration file for all application settings
- `load_config()` function with error handling and default value fallback
- Warning messages when config file is missing or malformed
## [0.1.0] - 2025-12-04
### Added
- Initial release of Series 4 Ingest Agent
- Micromate (Series 4) heartbeat monitoring for Seismo Fleet Manager
- THORDATA folder scanner for MLG files
- Automatic detection of newest MLG file per unit by timestamp
- Age calculation and status classification (OK / LATE / STALE)
- Console heartbeat display with formatted output
- Optional HTTP POST to SFM backend endpoint
- Configurable age thresholds and scan intervals
- Debug logging capability
- Graceful error handling for filesystem and network operations