Conversation
Runs the ADIF parse and insert loop asynchronously via QtConcurrent::run() on a background worker with its own dedicated database connection. Progress and cooperative cancellation are marshaled back to the UI thread via QFutureWatcher. Key improvements and fixes: - Offloads ADIF parsing and insertion off the UI thread (fixes #17) - Batches database inserts in 500-record transactions (kImportBatchSize) to optimize SQLite WAL write performance and yield write locks to readers - Throttles progress updates to every 25 records (kProgressUpdateStride) to eliminate UI thread event-queue congestion - Introduces setImportLock() to lock down table editing, deletions, filter bar, and settings without interrupting digital mode UDP listeners (WSJT-X) - Safely manages QProgressDialog lifetime via QPointer and deleteLater() - Guards against QFutureWatcher::result() assertion abort on canceled imports - Passes station location parameters (grid, lat, lon) by value to avoid repeated QSettings disk/registry queries on worker threads - Configures PRAGMA busy_timeout=5000 on SqliteBackend for concurrent access - Routes File -> Exit through MainWindow::close() so in-progress guards run Fixes #17 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
HamlibBackend's 500ms poll issued up to three blocking Hamlib calls per cycle, and connectRadio()/disconnectRadio() also blocked; a slow or unresponsive serial/network link could stall the whole UI for seconds. Moves the HamlibBackend instance to its own QThread so Hamlib I/O never runs on the UI thread, per the approved RFC in #18. RadioBackend::connectRadio()/disconnectRadio() are now slots so MainWindow's call sites dispatch via QMetaObject::invokeMethod(..., Qt::QueuedConnection) instead of calling across thread affinity directly. HamlibBackend drops its QObject parent (moveToThread() refuses parented objects) and MainWindow owns/deletes it explicitly; m_connected becomes atomic since it's read on the UI thread and written on the worker thread. Independently reviewed by Antigravity/Gemini and Claude Code, each verifying the other's claims rather than deferring to them. Fixes that came out of that review: - poll() now tracks consecutive read failures and disconnects with an error() after 3, instead of silently retrying forever with a stale "Connected" indicator on a dead rig - connectRadio() dropped its always-unused bool return - HamlibBackend's four public slots assert same-thread-affinity, so a future cross-thread misuse fails loudly instead of racing silently on the RIG* handle - Connect actions now disable eagerly on click and re-enable on a failed attempt, closing a re-entrancy window that async dispatch opened (a double-click could previously queue two connects) - The destructor's blocking teardown (a known, accepted limitation — Hamlib's blocking C API has no cancellation hook) is now documented explicitly at the point it applies Remaining architectural debt (the whole-object moveToThread() shape, and the exit-time blocking teardown) is tracked in #22 for a proper Worker-Object split rather than blocking this PR. Fixes #18 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- currentBackendConfig() now resolves through Settings::resolvedSqlitePath(), honoring a user-configured custom SQLite path instead of always using the AppLocalDataLocation default (previously ignored entirely). - setMigrationLock() now disables m_logView, m_filterBar, m_exportAdifAction, and m_settingsAction while a migration is running, mirroring the UI coverage setImportLock() already has. - Fixed a split-brain hazard found in adversarial review: onImportAdif() now targets the database m_db was actually opened with (cached in m_activeDbConfig/m_activeDbBackendKey) instead of re-reading Settings, which could otherwise point a background import at a different database than the one currently open if Settings changed mid-session. - currentBackendConfig() now creates the parent directory for custom SQLite paths too, not just the default location. - onExportAdif(), onEditQso(), onDeleteSelectedQso(), and onExportSelectedQsos() now check m_importLock/m_migrationLock for consistency with the rest of the lock coverage. Fixes #20. Independently reviewed and re-verified by Antigravity/Gemini (adversarial review + re-review approval on PR #23). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…er (#24) Implements the worker-object pattern proposed in #22. HamlibWorker now owns the RIG* handle, the poll timer, and every blocking Hamlib call, living on its own QThread. HamlibBackend goes back to being a normal QObject parented to MainWindow, so RadioBackend call sites in MainWindow no longer need QMetaObject::invokeMethod ceremony - connectRadio()/disconnectRadio() are plain virtual calls again, and MainWindow relies on Qt's parent-child lifetime management instead of manually deleting the backend. Shutdown keeps the same accepted tradeoff as PR #21: ~HamlibBackend() still blocks the UI thread waiting for the worker's current Hamlib call to finish, because Hamlib's blocking C API has no cancellation hook. Hardened per adversarial review (Antigravity): HamlibWorker now has a destructor that closes the rig defensively, the destructor's blocking disconnect call is guarded against a worker thread that never started or already stopped, and the shutdown-rationale comment was corrected to cite the real constraint - Qt refusing to destroy a QThread member while it's still running, not a RIG* use-after-free. Closes #22. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
chore: bump version to 26.9.1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First release of September 2026. Moves the two remaining sources of UI-thread blocking — ADIF import and Hamlib CAT I/O — onto background threads, and fixes a data-integrity gap in custom SQLite deployments.
Highlights
QtConcurrent::run()on a background worker with its own DB connection; progress and cooperative cancellation marshaled back viaQFutureWatcher; 500-record batched transactions.HamlibBackendno longer blocks the UI onrig_open/rig_get_freq/etc. First moved wholesale onto its ownQThread(feat(radio): move Hamlib CAT polling off the UI thread #21), then split into a worker-object design (refactor(radio): decouple HamlibBackend into UI facade and HamlibWorker #22, refactor(radio): decouple HamlibBackend into UI facade and HamlibWorker #24):HamlibWorkerowns theRIG*handle and every blocking call on a dedicated thread, whileHamlibBackendstays a normalQObjectparented toMainWindowwith the same synchronous-lookingRadioBackendAPI every other backend uses.Closes #17, #18, #20, #22.
See
ROADMAP.md's newv26.9.1entry for full detail.🤖 Generated with Claude Code