JS8Call-Improved master
Loading...
Searching...
No Matches
ActivityDB Class Reference

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
ActivityDBoperator= (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< CallRecordloadCalls (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.

Detailed Description

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.

Member Function Documentation

◆ begin()

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.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ clearConfig()

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.

Here is the call graph for this function:

◆ commit()

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.

Returns
True when the COMMIT succeeded.

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.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ copyConfig()

bool ActivityDB::copyConfig ( const QString & from,
const QString & to )

Copy everything stored for one configuration under a second configuration's id.

Parameters
fromThe id whose rows are read.
toThe id the copies are written under.
Returns
True when all three tables were copied.

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.

Here is the call graph for this function:

◆ deleteCall()

bool ActivityDB::deleteCall ( const QString & config,
const QString & band,
const QString & callsign )

Remove one stored call from a band.

Returns
True only when a row was actually removed.

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.

Here is the call graph for this function:

◆ error()

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".

◆ hasImported()

bool ActivityDB::hasImported ( const QString & config,
bool * ok = nullptr )

Whether the legacy .ini import has run for a configuration.

Parameters
configThe id to probe.
okWhen provided, reports whether the probe completed.
Returns
True when the marker row is present.
Here is the call graph for this function:

◆ isOpen()

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.

Here is the caller graph for this function:

◆ loadCalls()

QList< ActivityDB::CallRecord > ActivityDB::loadCalls ( const QString & config,
const QString & band,
bool * ok = nullptr )

Load a band's stored calls.

Parameters
okWhen 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.

Here is the call graph for this function:

◆ markImported()

bool ActivityDB::markImported ( const QString & config)

Record that the legacy .ini import has run.

Parameters
configThe id the marker is filed under.
Returns
True when the marker was written or already there.

Written inside the import's transaction, so a failed import rolls it back with the rows. clearConfig() deliberately leaves it.

Here is the call graph for this function:

◆ open()

bool ActivityDB::open ( )

Open the store, creating or validating its schema.

Returns
True when the handle is usable.

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.


The documentation for this class was generated from the following files: