Phosphor
Qt6 / Wayland library suite for window-management tools
 
Loading...
Searching...
No Matches
PhosphorConfig::JsonBackend Class Reference

Atomic-write JSON configuration backend. More...

#include <phosphor-config/include/PhosphorConfig/JsonBackend.h>

Inheritance diagram for PhosphorConfig::JsonBackend:
[legend]

Public Types

enum class  SyncPolicy { Synchronous , Deferred }
 Sync timing policy. See setSyncPolicy for the full contract. More...
 

Public Member Functions

 JsonBackend (QString filePath)
 Construct a backend backed by filePath.
 
 ~JsonBackend () override
 
std::unique_ptr< IGroupgroup (const QString &name) override
 Return a scoped view into the named group.
 
void reparseConfiguration () override
 Re-read configuration from disk, discarding any pending in-memory changes.
 
bool sync () override
 Request that pending writes reach disk.
 
bool commit () override
 Bypasses the Deferred sync policy's debounce so the return value is a real commit result under every policy.
 
void deleteGroup (const QString &name) override
 Delete an entire group and everything inside it.
 
QString readRootString (const QString &key, const QString &defaultValue={}) const override
 Read/write ungrouped (root-level) keys.
 
void writeRootString (const QString &key, const QString &value) override
 
void removeRootKey (const QString &key) override
 
QStringList groupList () const override
 Enumerate every top-level group name.
 
void setPathResolver (std::shared_ptr< IGroupPathResolver > resolver) override
 Attach a custom path resolver.
 
std::shared_ptr< IGroupPathResolverpathResolver () const override
 Currently-installed resolver, or nullptr when none is set (also the default for backends that ignore setPathResolver).
 
void setRootGroupName (const QString &name)
 Change the JSON object that receives ungrouped writeRootString calls.
 
QString rootGroupName () const
 
void setVersionStamp (const QString &key, int version) override
 Stamp-on-sync: if non-empty, every sync() that writes the file will insert key = version when the key is absent from the root.
 
std::pair< QString, int > versionStamp () const override
 Currently-installed version stamp as {key, version}.
 
void applyVersionStamp (QJsonObject &root) const
 Insert the installed version stamp into root when a stamp is configured and root does not already carry the key.
 
bool applyMigration (const Schema &schema) override
 Run schema's migration chain against the in-memory root, and if any step bumps the version stamp, atomically rewrite the backing file and refresh the in-memory state.
 
QJsonObject jsonRootSnapshot () const
 Read-only access to the in-memory document.
 
QString filePath () const
 Backing file path (useful for migration-chain callers that need to rewrite via writeJsonAtomically during schema upgrades).
 
void clearDirty ()
 Clear the dirty flag without writing.
 
bool isDirty () const override
 Whether the in-memory document carries changes that have not been committed to disk.
 
void replaceRoot (QJsonObject root)
 Replace the in-memory document with root.
 
void setSyncPolicy (SyncPolicy policy, int debounceMs=500)
 Configure when sync() commits to disk.
 
SyncPolicy syncPolicy () const
 
bool flushPending ()
 Flush to disk now, cancelling any pending deferred-sync timer first.
 
- Public Member Functions inherited from PhosphorConfig::IBackend
virtual ~IBackend ()=default
 
 IBackend (const IBackend &)=delete
 
IBackendoperator= (const IBackend &)=delete
 

Static Public Member Functions

static bool writeJsonAtomically (const QString &filePath, const QJsonObject &root)
 Atomically write root to filePath (temp file + rename via QSaveFile).
 

Additional Inherited Members

- Protected Member Functions inherited from PhosphorConfig::IBackend
 IBackend ()=default
 

Detailed Description

Atomic-write JSON configuration backend.

Reads and writes a single JSON document. Writes are deferred until sync() is called and then committed via QSaveFile (temp file + rename, safe against crashes and power loss).

Group resolution order:

  1. If an IGroupPathResolver is installed and returns a path, use it.
  2. If the group name contains a dot, treat as a dot-path hierarchy.
  3. Otherwise treat it as a flat top-level object name.

The "root group" (see rootGroupName) holds ungrouped keys written via writeRootString. Defaults to "General" for compatibility with QSettings INI behaviour.

Member Enumeration Documentation

◆ SyncPolicy

Sync timing policy. See setSyncPolicy for the full contract.

Enumerator
Synchronous 

sync() flushes the dirty root to disk synchronously and returns the commit result.

Default — matches the pre-policy behaviour so switching an existing consumer requires no change.

Deferred 

sync() restarts a single-shot debounce timer and returns true without touching disk.

The actual flush happens on the timer's timeout, on the same thread that owns the backend (i.e. whichever thread is pumping the event loop that owns the timer). Callers that need a guaranteed-committed state before proceeding (e.g. closing a save dialog, shutting down a daemon) call flushPending() to force an immediate flush. The backend's destructor also flushes pending writes.

Requires a running event loop to actually fire. Tests that exercise this path must call QTest::qWait() or QCoreApplication::processEvents() after sync() to let the timer elapse, or call flushPending() directly.

Constructor & Destructor Documentation

◆ JsonBackend()

PhosphorConfig::JsonBackend::JsonBackend ( QString  filePath)
explicit

Construct a backend backed by filePath.

The file is loaded immediately; missing or malformed files start with an empty root object (a warning is logged for malformed content).

◆ ~JsonBackend()

PhosphorConfig::JsonBackend::~JsonBackend ( )
override

Member Function Documentation

◆ applyMigration()

bool PhosphorConfig::JsonBackend::applyMigration ( const Schema schema)
overridevirtual

Run schema's migration chain against the in-memory root, and if any step bumps the version stamp, atomically rewrite the backing file and refresh the in-memory state.

Returns true unless the atomic disk rewrite fails — in which case the backend stays at the unmigrated state and the next successful sync() retries. Consumers that need the "did migrations actually apply" bit should check versionStamp() before and after, not this return value.

Reimplemented from PhosphorConfig::IBackend.

◆ applyVersionStamp()

void PhosphorConfig::JsonBackend::applyVersionStamp ( QJsonObject &  root) const

Insert the installed version stamp into root when a stamp is configured and root does not already carry the key.

No-op otherwise.

This is the same step sync() applies on its way to disk, exposed so that a caller writing a root snapshot out of band (an export, say) can stamp it identically. A document written without the stamp reads back as version-less and re-runs the whole migration chain when it is imported.

◆ clearDirty()

void PhosphorConfig::JsonBackend::clearDirty ( )

Clear the dirty flag without writing.

Used by migration (applyMigration) and by callers that stage something into the root, take a snapshot of it, then restore the root and need to put the flag back the way they found it (see isDirty()).

◆ commit()

bool PhosphorConfig::JsonBackend::commit ( )
overridevirtual

Bypasses the Deferred sync policy's debounce so the return value is a real commit result under every policy.

Equivalent to flushPending().

Reimplemented from PhosphorConfig::IBackend.

◆ deleteGroup()

void PhosphorConfig::JsonBackend::deleteGroup ( const QString &  name)
overridevirtual

Delete an entire group and everything inside it.

Intermediate parents are pruned if they become empty (dot-path groups only).

Implements PhosphorConfig::IBackend.

◆ filePath()

QString PhosphorConfig::JsonBackend::filePath ( ) const

Backing file path (useful for migration-chain callers that need to rewrite via writeJsonAtomically during schema upgrades).

◆ flushPending()

bool PhosphorConfig::JsonBackend::flushPending ( )

Flush to disk now, cancelling any pending deferred-sync timer first.

No-op only when the backend is not dirty — under Synchronous policy there is no timer to cancel, but a dirty root is still written, so this is a commit under every policy rather than a Deferred-only escape hatch. Returns the commit result (or true when there was nothing to flush). Safe to call from any context that could call sync().

◆ group()

std::unique_ptr< IGroup > PhosphorConfig::JsonBackend::group ( const QString &  name)
overridevirtual

Return a scoped view into the named group.

Caller owns the pointer; destroy it before asking for another group on this backend.

Implements PhosphorConfig::IBackend.

◆ groupList()

QStringList PhosphorConfig::JsonBackend::groupList ( ) const
overridevirtual

Enumerate every top-level group name.

Dot-path groups are returned with their full path ("Snapping", "Snapping.Behavior", ...). Groups produced by a plugged-in IGroupPathResolver appear in the resolver's preferred external form.

CONTRACT — reserved names. Whatever container an implementation uses to hold the ungrouped keys that readRootString / writeRootString / removeRootKey address must NOT appear in this list, nor may any of its descendants. Ungrouped keys are not addressable through group() and no caller can tell them apart from a real group by name, so a consumer that sweeps groupList() and calls deleteGroup on every name it does not recognise would wipe them. Enforcing it here rather than asking every consumer to know each backend's container name is what makes such a sweep safe.

The same applies to any root key an IGroupPathResolver reserves: the resolver owns that subtree exclusively and enumerates it itself.

Implementations: JsonBackend filters its rootGroupName (and the version stamp, and the resolver's reserved roots) out of the enumeration.

Implements PhosphorConfig::IBackend.

◆ isDirty()

bool PhosphorConfig::JsonBackend::isDirty ( ) const
overridevirtual

Whether the in-memory document carries changes that have not been committed to disk.

Pairs with clearDirty() for callers that snapshot/restore the root (see jsonRootSnapshot / replaceRoot) and need to put the flag back the way they found it.

Reimplemented from PhosphorConfig::IBackend.

◆ jsonRootSnapshot()

QJsonObject PhosphorConfig::JsonBackend::jsonRootSnapshot ( ) const

Read-only access to the in-memory document.

Implicitly shared (COW), so the copy is cheap and isolated from future mutations.

◆ pathResolver()

std::shared_ptr< IGroupPathResolver > PhosphorConfig::JsonBackend::pathResolver ( ) const
overridevirtual

Currently-installed resolver, or nullptr when none is set (also the default for backends that ignore setPathResolver).

Reimplemented from PhosphorConfig::IBackend.

◆ readRootString()

QString PhosphorConfig::JsonBackend::readRootString ( const QString &  key,
const QString &  defaultValue = {} 
) const
overridevirtual

Read/write ungrouped (root-level) keys.

Implements PhosphorConfig::IBackend.

◆ removeRootKey()

void PhosphorConfig::JsonBackend::removeRootKey ( const QString &  key)
overridevirtual

◆ reparseConfiguration()

void PhosphorConfig::JsonBackend::reparseConfiguration ( )
overridevirtual

Re-read configuration from disk, discarding any pending in-memory changes.

Must not be called while a group view is live.

Discarding is load-bearing, not incidental: callers use this to roll back after a failed write, so an implementation that instead FLUSHED the pending changes would persist the very mutations the caller is asking it to drop. A store that cannot drop unsaved writes therefore cannot implement IBackend — which is why QSettings, whose destructor flushes and which offers no discard API, is confined to the read-only QSettingsBackend::readConfigFromDisk helper.

Implements PhosphorConfig::IBackend.

◆ replaceRoot()

void PhosphorConfig::JsonBackend::replaceRoot ( QJsonObject  root)

Replace the in-memory document with root.

Marks the backend dirty — callers who paired this with a successful writeJsonAtomically may call clearDirty() immediately after to restore the "in-memory matches disk" invariant. Must not be called while any JsonGroup views are alive.

◆ rootGroupName()

QString PhosphorConfig::JsonBackend::rootGroupName ( ) const

◆ setPathResolver()

void PhosphorConfig::JsonBackend::setPathResolver ( std::shared_ptr< IGroupPathResolver resolver)
overridevirtual

Attach a custom path resolver.

Passing nullptr removes it. Safe to call at any time; the resolver is consulted on every group access, so it should be cheap.

Reimplemented from PhosphorConfig::IBackend.

◆ setRootGroupName()

void PhosphorConfig::JsonBackend::setRootGroupName ( const QString &  name)

Change the JSON object that receives ungrouped writeRootString calls.

Defaults to "General". Call before the first write.

◆ setSyncPolicy()

void PhosphorConfig::JsonBackend::setSyncPolicy ( SyncPolicy  policy,
int  debounceMs = 500 
)

Configure when sync() commits to disk.

debounceMs is only consulted for Deferred policy; it sets the coalescing window during which repeated sync() calls restart the same timer. Values <= 0 are clamped to 1 ms.

Safe to call at any time. Switching from Deferred back to Synchronous also flushes any pending deferred write so the in-memory and on-disk state agree on return.

◆ setVersionStamp()

void PhosphorConfig::JsonBackend::setVersionStamp ( const QString &  key,
int  version 
)
overridevirtual

Stamp-on-sync: if non-empty, every sync() that writes the file will insert key = version when the key is absent from the root.

Used by consumers who pair a JsonBackend with a Schema so fresh stores carry the current version from day one. Default disables the behaviour (empty key).

Reimplemented from PhosphorConfig::IBackend.

◆ sync()

bool PhosphorConfig::JsonBackend::sync ( )
overridevirtual

Request that pending writes reach disk.

No-op when nothing is dirty. Returns true on success (or when there was nothing to flush), false on an I/O error — backends log the reason before returning.

true means "accepted", NOT "committed": a backend is free to satisfy this by scheduling the write (JsonBackend's Deferred sync policy debounces it), in which case the write has not happened yet and may still fail. Callers that must know the bytes landed — anything moving a baseline or reporting success to a user — call commit() instead.

Implements PhosphorConfig::IBackend.

◆ syncPolicy()

SyncPolicy PhosphorConfig::JsonBackend::syncPolicy ( ) const

◆ versionStamp()

std::pair< QString, int > PhosphorConfig::JsonBackend::versionStamp ( ) const
overridevirtual

Currently-installed version stamp as {key, version}.

Returns an empty key when no stamp is installed. Used by shared-backend safety checks (see Store::Store) to detect a clobber.

Reimplemented from PhosphorConfig::IBackend.

◆ writeJsonAtomically()

static bool PhosphorConfig::JsonBackend::writeJsonAtomically ( const QString &  filePath,
const QJsonObject &  root 
)
static

Atomically write root to filePath (temp file + rename via QSaveFile).

Exposed for use by MigrationRunner and other external callers that already have a QJsonObject in hand. Returns true on success.

◆ writeRootString()

void PhosphorConfig::JsonBackend::writeRootString ( const QString &  key,
const QString &  value 
)
overridevirtual

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