Seamless ADIF logbook auto-reload for DXCC spot colouring#1801
Conversation
…ate) Previously the QFileSystemWatcher was opt-in via a manual "Auto-Reload Log" toggle that defaulted to off, so spot DXCC colours never updated when the user exported a new log without clicking Browse again. Changes: - MainWindow startup: always arm the watcher when a saved ADIF path exists. No longer conditional on the DxccAutoReloadAdif setting. - DxClusterDialog: removed the "Auto-Reload Log" toggle; the watcher is armed automatically whenever the user selects a file via Browse. The logger just exports to the same path and spot colours update within 2 s. - DxccColorProvider: add importStarted() signal emitted at the start of every async parse so the UI can show "Updating…" while the file is being read. Wire importStarted → "Updating…" label in the dialog; importFinished continues to update the QSO / entity count as before. - Fix Windows inode-replacement robustness: move the QFileSystemWatcher addPath() re-arm from the fileChanged handler into the debounce timer callback. Loggers that atomically rename a temp file over the destination briefly delete the watched path; the debounce fires 2 s later by which time the replacement file is present, so addPath() succeeds and subsequent exports are also caught. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three robustness improvements on top of the initial auto-reload commit:
1. Directory watcher (delete→recreate)
QFileSystemWatcher stops receiving events once the watched file is
deleted. A directory watcher on the parent folder stays alive and
re-arms the file watcher the moment the target filename reappears,
so exports to a previously-deleted path are still caught.
2. File-locking guard (Windows logger lock)
On Windows some loggers hold an exclusive write-lock while exporting.
AdifParser::parseFileAsync now retries QFile::open() up to 3 times
at 500 ms intervals before giving up, covering the typical lock
window without blocking the worker thread unnecessarily.
3. openFailed signal — preserve worked status on all-retry failure
Instead of emitting finished({}) (which would wipe spot colours),
a new AdifParser::openFailed signal is emitted when all open attempts
fail. DxccColorProvider::onParseFailed keeps m_workedStatus intact
and only re-arms the debounce timer if the file still exists (locked,
not deleted), preventing an infinite retry loop on intentional deletion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Nice work, @Chaosuk97 — this is a well-structured improvement that addresses a real usability pain point. The behaviour matrix in the PR description is appreciated; it's clear you've thought through the edge cases. A few observations:
Looks good
- The retry-with-backoff in
AdifParser::parseFileAsyncis sensible for the write-lock scenario and correctly runs on the worker thread (no GUI stall). TheopenFailedsignal is a clean way to preserve existing worked status on failure. - Moving watcher re-registration from
fileChangedinto the debounce timeout callback is the right fix for atomic-rename exports — by the time the 2s window expires, the replacement file is present and the new inode gets tracked. - Directory watcher for delete→recreate is a good addition. The guard (
QFileInfo::exists+!m_fileWatcher.files().contains) keeps it cheap even in busy directories. - The removal of the toggle simplifies both the code and the UX. Always-on watching after a Browse selection is the right default.
Minor items to consider (non-blocking)
-
Directory watcher noise in busy folders: If the user's ADIF file lives in a directory with heavy write traffic (e.g. a logging program's working directory with temp files),
directoryChangedwill fire for every unrelated file event. The current guard is lightweight (exists+containscheck), so this is fine in practice, but worth noting in case users report CPU spikes on certain setups. A future optimisation could compareQFileInfo::lastModifiedbefore re-arming. -
Orphaned
DxccAutoReloadAdifsetting: The toggle's AppSettings key (DxccAutoReloadAdif) is no longer read or written, but existing users will have it sitting in their settings file. Not a bug — just a minor cleanup opportunity if you want to add a one-liner to remove it on startup or in a future migration pass. -
Flicker on locked-file retry: When
onParseFailedfires, it emitsimportFinished(clearing "Updating..."), then re-arms the debounce timer. Two seconds laterimportAdifFileemitsimportStarted("Updating..." again). On a file that stays locked for multiple retry cycles the stats label will toggle between "Updating..." and the old counts every ~3.5s. This is a cosmetic edge case — one option would be to not emitimportFinishedfromonParseFailedwhen the debounce timer is being re-armed (i.e. keep the "Updating..." state until we actually succeed or give up). -
parseFile()static method now diverges fromparseFileAsync(): The async path gained retry logic and callsparse(f.readAll())directly, while the staticparseFile()still does a single open-or-fail. That's fine for the current usage (onlyparseFileAsyncis called from the watcher path), but worth keeping in mind ifparseFile()is used elsewhere in the future.
All changes are within the stated scope (ADIF auto-reload plumbing), no files outside that boundary. Uses AppSettings correctly where applicable. No obvious null-pointer or resource-leak risks — the QFileSystemWatcher and QTimer are member-owned with proper cleanup in setAutoReload.
Thanks for the contribution!
ten9876
left a comment
There was a problem hiding this comment.
Comprehensive edge-case handling: atomic rename (re-arm after debounce), write-lock (3× retry), delete-recreate (directory watcher), parse failure (preserves worked status). Worker-thread msleep is correct. Settings migration is silent/non-destructive. Author's call to remove the toggle makes sense given the broken-either-way state it was in. LGTM.
) Cuts a community-contribution-heavy point release. Eight community PRs landed plus three AetherClaude crash fixes. Highlights: Community: - Fix RADE RX not decoding on Windows (#1820, NF0T) - Clamp stale DAX RX backlog on macOS — fixes +1.5s FT8 DT bias (#1822, jensenpat) - Fix TCI DAX resampler crosstalk between slices (#1815, Chaosuk97) - Fix RX applet pan slider with NR active (#1799, Chaosuk97) - Fix TCI RX gain default 1.0 → 0.5 to match applet (#1811, NF0T) - Fix HAVE_SERIALPORT guard (#1812, NF0T) - Seamless ADIF logbook auto-reload (#1801, Chaosuk97) - RAC Canada band-plan corrections (#1817, VE3NEM) AetherClaude crash fixes: - Applet reorder with floating containers (#1745 → #1746) - Lazy-build RadioSetupDialog tabs, dodges Wayland FFmpeg/VDPAU crash (#1776 → #1777) - PanAdapter float-freeze — show-after-reset + direct reparent (#1668 → #1669) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
.adi/.adiffile once and spot colours stay current automatically.How to use
.adior.adif)Changes
src/gui/DxClusterDialog.cppsrc/gui/MainWindow.cppsrc/core/DxccColorProvider(.h/.cpp)importStarted()signal so the UI can show "Updating…" during async parsingAdifParser::openFailedsignal: if the file is locked by the logger during export,parseFileAsyncretries 3× at 500 ms intervals on the worker thread rather than immediately emitting an empty result that would wipe spot colours. On all-retry failureonParseFailedpreserves the existing worked status and only re-arms the debounce timer if the file still exists (locked not deleted), preventing an infinite retry loop on intentional deletionBehaviour matrix
Test plan
🤖 Generated with Claude Code