|
JS8Call-Improved master
|
Persistent per-band activity storage (activity.db3). More...
#include <ActivityDB.h>
Classes | |
| struct | CallRecord |
| One persisted Call Activity row - the full CallDetail field set, so a band round-trip within a session loses nothing (the legacy .ini group stored only a subset). More... | |
Public Member Functions | |
| ActivityDB (const QString &path) | |
| ActivityDB (const ActivityDB &)=delete | |
| ActivityDB & | operator= (const ActivityDB &)=delete |
| bool | open () |
| Open the store, creating or validating its schema. | |
| void | close () |
| bool | isOpen () const |
| Whether the handle is usable. | |
| QString | error () const |
| The message captured at the most recent failure. | |
| bool | begin () |
| Batch several writes into a single transaction (legacy import, QSY offset rewrites). | |
| bool | commit () |
| Commit the open transaction. | |
| void | rollback () |
| Roll the open transaction back. | |
| bool | inTransaction () const |
| Whether a transaction is currently open. | |
| bool | upsertCall (const QString &config, const QString &band, const CallRecord &record) |
| bool | deleteCall (const QString &config, const QString &band, const QString &callsign) |
| Remove one stored call from a band. | |
| bool | deleteCalls (const QString &config, const QString &band) |
| QList< CallRecord > | loadCalls (const QString &config, const QString &band, bool *ok=nullptr) |
| Load a band's stored calls. | |
| bool | saveRxText (const QString &config, const QString &band, const QString &html) |
| QString | loadRxText (const QString &config, const QString &band, bool *ok=nullptr) |
| bool | clearRxText (const QString &config, const QString &band) |
| bool | hasImported (const QString &config, bool *ok=nullptr) |
| Whether the legacy .ini import has run for a configuration. | |
| bool | markImported (const QString &config) |
| Record that the legacy .ini import has run. | |
| bool | clearConfig (const QString &config) |
| Wipe everything stored for a configuration. | |
| bool | copyConfig (const QString &from, const QString &to) |
| Copy everything stored for one configuration under a second configuration's id. | |
Persistent per-band activity storage (activity.db3).
Stores the Call Activity table and the RX text history in a dedicated SQLite database, keyed by (configuration, band, callsign) so that activity heard on one band can never be attributed to another (issue #267), and keyed by configuration name so that MultiSettings configurations each keep their own activity, mirroring the way each configuration keeps its own settings group in the .ini. The key is a UUID each configuration generates once into its own settings, so renames follow automatically, a settings reset orphans the old rows and starts clean, and nothing ever infers that stored rows should be deleted or moved. Deleting a configuration leaves its rows behind (orphaned, never shown); a clone mints an id of its own and takes a copy of its source's rows under it at its first start, so the two then diverge.
Writes happen as activity arrives (write-on-change), not at shutdown, so a crash, power loss, or SIGKILL loses at most the in-flight row rather than everything since the last clean close; open() covers the journal and synchronous levels that keep those writes cheap and safe.
Rows are never aged out of the store. The callsign-aging setting is applied to what a band load contributes, bounding what a session shows, while the store itself keeps everything - so long-term activity survives restarts and upgrades without flooding the table.
The legacy [CallActivity] group and RXActivity key in the .ini are imported once per configuration by ActivityStorageController, which knows the ini layout and the band plan, and left in place for older versions of the software, following the inbox_v1 -> inbox_v2 migration pattern. The fire-once marker is a row here keyed by the configuration id, so it follows the data.
| bool ActivityDB::begin | ( | ) |
Batch several writes into a single transaction (legacy import, QSY offset rewrites).
A failed begin() leaves autocommit in effect, so the individual writes still land - just unbatched.


| bool ActivityDB::clearConfig | ( | const QString & | config | ) |
Wipe everything stored for a configuration.
The startup "Reset ... Call Activity and RX History" behavior, the user-facing "Clear All Activity" actions, and the fresh-start wipe after a configuration reset. Both tables or neither: where a transaction cannot be opened it does nothing, rather than destroy one table and fail on the other.

| bool ActivityDB::commit | ( | ) |
Commit the open transaction.
A failed commit() rolls the batch back itself, so the connection can never be left stuck inside a transaction that would silently swallow every later write.
A successful COMMIT is not evidence that the store recovered: only a successful data statement is, and any of those has already cleared both the strike count and any deferred close. This path therefore does not go through noteResult() at all - resetting the strikes here would let an all-failing batch that commits cleanly hold the counter at zero forever, and the handle would never give up.
A failed COMMIT (SQLITE_BUSY on the non-WAL fallback's lock upgrade, say) leaves the transaction open, so it is explicitly rolled back: without that, every later autocommit write would silently join the open transaction and be lost wholesale when the handle closes. It also counts as a failure strike, because the successful steps inside a doomed transaction reset the counter and a store whose commits persistently fail would otherwise never report unusable.


| bool ActivityDB::copyConfig | ( | const QString & | from, |
| const QString & | to ) |
Copy everything stored for one configuration under a second configuration's id.
| from | The id whose rows are read. |
| to | The id the copies are written under. |
The clone half of the scheme above: a cloned configuration mints its own id and takes a private copy of its source's rows, rather than sharing them. Rows the destination already holds win, because a clone whose copy was deferred by a degraded start may have written newer ones of its own in the meantime. All three tables or none: the copy runs in one transaction, so a failure leaves nothing half-copied and the whole copy retries at the next start. That holds when the copy opens the transaction itself; inside a caller's transaction a failure is only reported, and the caller's rollback governs.

| bool ActivityDB::deleteCall | ( | const QString & | config, |
| const QString & | band, | ||
| const QString & | callsign ) |
Remove one stored call from a band.
A statement that ran but matched nothing (the row is filed under another band) reports false, so the caller can tell the user rather than appear to have deleted something. error() is empty in that case, and carries a message only when the statement itself failed.

| QString ActivityDB::error | ( | ) | const |
The message captured at the most recent failure.
Meaningful after any call here returns false - including after the failing call ran further (successful) statements such as a rollback, which would have reset sqlite3_errmsg() to "not an error".
| bool ActivityDB::hasImported | ( | const QString & | config, |
| bool * | ok = nullptr ) |
Whether the legacy .ini import has run for a configuration.
| config | The id to probe. |
| ok | When provided, reports whether the probe completed. |

| bool ActivityDB::isOpen | ( | ) | const |
Whether the handle is usable.
False after a failed open(), and after enough consecutive read/write failures that the handle closed itself - so a store that breaks mid-session (vanished volume, creeping corruption) eventually reports unusable instead of failing every call forever.

| QList< ActivityDB::CallRecord > ActivityDB::loadCalls | ( | const QString & | config, |
| const QString & | band, | ||
| bool * | ok = nullptr ) |
Load a band's stored calls.
| ok | When provided, reports whether the read completed. |
A read error is not the same as an empty band, so a transient I/O failure can be kept from being treated as - and then overwriting - genuinely absent data.

| bool ActivityDB::markImported | ( | const QString & | config | ) |
Record that the legacy .ini import has run.
| config | The id the marker is filed under. |
Written inside the import's transaction, so a failed import rolls it back with the rows. clearConfig() deliberately leaves it.

| bool ActivityDB::open | ( | ) |
Open the store, creating or validating its schema.
WAL keeps an interrupted write from corrupting the store, and with synchronous=NORMAL commits are not individually fsynced, so frequent small writes stay cheap on the GUI thread while remaining safe against application crashes - an OS crash can lose, but not corrupt, the most recent commits. NORMAL carries that guarantee only under WAL, and WAL can be refused (filesystems without shared-memory support, such as network homes), so the sync level is relaxed only after checking what actually took effect. The busy timeout is set before that probe: the first WAL conversion takes a lock another connection may briefly hold, and probing without the timeout would silently leave the session on the rollback journal.
The schema is created in one transaction, so a power cut cannot leave it half-built. Preparing the hot-path statements here doubles as a schema validation probe: CREATE TABLE IF NOT EXISTS silently accepts a pre-existing table with different columns, say after a version downgrade, and without this check every later write would fail with its return value unexamined - the application would look normal all session while persisting nothing.