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

Generic JSON-directory loader with debounced live reload. More...

#include <phosphor-fsloader/include/PhosphorFsLoader/DirectoryLoader.h>

Inheritance diagram for PhosphorFsLoader::DirectoryLoader:
[legend]

Classes

struct  Entry
 Tracked entry — mirrors ParsedEntry minus the payload. More...
 

Signals

void entriesChanged ()
 Fired after every rescan, coalesced by the 50 ms debounce — regardless of whether the discovered entry set or any underlying payload actually changed.
 

Public Member Functions

 DirectoryLoader (IDirectoryLoaderSink &sink, QObject *parent=nullptr)
 Construct with a borrowed sink.
 
 ~DirectoryLoader () override
 
 DirectoryLoader (const DirectoryLoader &)=delete
 
DirectoryLoaderoperator= (const DirectoryLoader &)=delete
 
int loadFromDirectory (const QString &directory, LiveReload liveReload=LiveReload::Off)
 Register a directory for scanning + (optionally) watching.
 
int loadFromDirectories (const QStringList &directories, LiveReload liveReload=LiveReload::Off, RegistrationOrder order=RegistrationOrder::LowestPriorityFirst)
 Register multiple directories in caller-declared priority order.
 
void requestRescan ()
 Trigger a debounced rescan.
 
int registeredCount () const
 Count of entries currently tracked by the loader.
 
QList< Entryentries () const
 Current entries (post last rescan), sorted by key for deterministic order across platforms and Qt versions.
 
void setDebounceIntervalForTest (int ms)
 Test-only: override the debounce interval (default 50 ms).
 
void setMaxEntriesForTest (int cap)
 Test-only: override the per-rescan entry cap (default kMaxEntries).
 
QString watchedAncestorForTest (const QString &target) const
 Test-only: ancestor-watch introspection forwarded to the underlying WatchedDirectorySet.
 
bool hasParentWatchForTest (const QString &path) const
 Test-only: parent-watch introspection forwarded to the underlying WatchedDirectorySet.
 

Static Public Attributes

static constexpr qint64 kMaxFileBytes = 1 * 1024 * 1024
 Per-file size cap.
 
static constexpr int kMaxEntries = 10'000
 Hard cap on entries parsed per rescan (summed across every registered directory).
 

Detailed Description

Generic JSON-directory loader with debounced live reload.

A thin specialisation of WatchedDirectorySet that scans top-level *.json in every registered directory, applies user-wins layering on key collision (caller passes dirs system-first / user-last per the loadFromDirectories docstring), and dispatches per-file parsing + batch commits to a consumer-supplied IDirectoryLoaderSink.

Watching, debouncing, parent-watch promotion, and rescan-during-rescan race handling all live in the underlying WatchedDirectorySetDirectoryLoader adds only the JSON-specific concerns:

• Top-level *.json filtering. • Per-file size cap (kMaxFileBytes) and per-rescan entry cap (kMaxEntries) DoS guards. • Sink dispatch (parseFile per file, one commitBatch per scan). • Stale-entry purge: deleted files' keys are reported to the sink as removedKeys so the sink can unregister them.

Loaders that need a different on-disk shape (subdirectory layouts, non-JSON file extensions, custom filename validation) implement IScanStrategy directly against WatchedDirectorySet rather than extending this class.

Thread safety

GUI-thread only. Inherits the threading constraint from WatchedDirectorySet.

Constructor & Destructor Documentation

◆ DirectoryLoader() [1/2]

PhosphorFsLoader::DirectoryLoader::DirectoryLoader ( IDirectoryLoaderSink sink,
QObject *  parent = nullptr 
)
explicit

Construct with a borrowed sink.

sink must outlive the loader. Taken by reference rather than raw pointer so there is no need for a null-check at the call site — "sink is always valid" is a compile-time guarantee.

◆ ~DirectoryLoader()

PhosphorFsLoader::DirectoryLoader::~DirectoryLoader ( )
override

◆ DirectoryLoader() [2/2]

PhosphorFsLoader::DirectoryLoader::DirectoryLoader ( const DirectoryLoader )
delete

Member Function Documentation

◆ entries()

QList< Entry > PhosphorFsLoader::DirectoryLoader::entries ( ) const

Current entries (post last rescan), sorted by key for deterministic order across platforms and Qt versions.

◆ entriesChanged

void PhosphorFsLoader::DirectoryLoader::entriesChanged ( )
signal

Fired after every rescan, coalesced by the 50 ms debounce — regardless of whether the discovered entry set or any underlying payload actually changed.

This is deliberately a "rescan completed" signal rather than a "content changed" signal — the loader has no visibility into the sink's payload semantics, so it cannot diff at this layer. Sinks that need change-only consumer signals layer their own diff on top: CurveLoader and ProfileLoader each maintain a lastBatchChanged flag in their sink and gate curvesChanged / profilesChanged on it.

Tests and debug tooling rely on the per-rescan emission to observe rescans without payload inspection — do not weaken this contract without updating the loader-sink consumers and the test suite. The three sister registries (ShaderRegistry, AnimationShaderRegistry, ScriptedAlgorithmLoader) gate their public content-changed signals at the registry level via SHA-1 signature or QHash diff because they own their parse output; DirectoryLoader gates one layer up.

◆ hasParentWatchForTest()

bool PhosphorFsLoader::DirectoryLoader::hasParentWatchForTest ( const QString &  path) const

Test-only: parent-watch introspection forwarded to the underlying WatchedDirectorySet.

◆ loadFromDirectories()

int PhosphorFsLoader::DirectoryLoader::loadFromDirectories ( const QStringList &  directories,
LiveReload  liveReload = LiveReload::Off,
RegistrationOrder  order = RegistrationOrder::LowestPriorityFirst 
)

Register multiple directories in caller-declared priority order.

RegistrationOrder::LowestPriorityFirst (the default) takes input in [sys-lowest, ..., sys-highest, user] order — the same shape the daemon's setupAnimationProfiles already builds via std::reverse(locateAll(...)) + user-dir append. Passing HighestPriorityFirst lets callers feed locateAll's natural output (with the user dir prepended) directly without their own pre-reverse — the base normalises before the strategy runs, so higher-priority entries always override on key collision.

Same one-way liveReload semantics as loadFromDirectory.

Empty directories is a no-op: no scan runs and the return value is the count of entries currently tracked from PRIOR registrations (not zero). Callers needing "force a rescan with no new dirs" should use requestRescan() instead.

◆ loadFromDirectory()

int PhosphorFsLoader::DirectoryLoader::loadFromDirectory ( const QString &  directory,
LiveReload  liveReload = LiveReload::Off 
)

Register a directory for scanning + (optionally) watching.

Idempotent on the directory path — adding the same directory twice is a no-op on the second call. Returns the count of entries CURRENTLY registered after the scan (not the delta).

liveReload is a one-way enable: once any call passes LiveReload::On, the loader keeps watching for the rest of its lifetime.

The default is Off because this is a library primitive — tests and batch imports compose against it. Consumer wrappers above this (CurveLoader/ProfileLoader and the shader registries) inherit the default, but can override it via the explicit LiveReload::On argument production callers pass.

◆ operator=()

DirectoryLoader & PhosphorFsLoader::DirectoryLoader::operator= ( const DirectoryLoader )
delete

◆ registeredCount()

int PhosphorFsLoader::DirectoryLoader::registeredCount ( ) const

Count of entries currently tracked by the loader.

◆ requestRescan()

void PhosphorFsLoader::DirectoryLoader::requestRescan ( )

Trigger a debounced rescan.

Forwards to the underlying WatchedDirectorySet.

◆ setDebounceIntervalForTest()

void PhosphorFsLoader::DirectoryLoader::setDebounceIntervalForTest ( int  ms)

Test-only: override the debounce interval (default 50 ms).

◆ setMaxEntriesForTest()

void PhosphorFsLoader::DirectoryLoader::setMaxEntriesForTest ( int  cap)

Test-only: override the per-rescan entry cap (default kMaxEntries).

Lets the cap regression test trip the guard with 3-digit file counts rather than having to materialise 10k files.

◆ watchedAncestorForTest()

QString PhosphorFsLoader::DirectoryLoader::watchedAncestorForTest ( const QString &  target) const

Test-only: ancestor-watch introspection forwarded to the underlying WatchedDirectorySet.

Member Data Documentation

◆ kMaxEntries

constexpr int PhosphorFsLoader::DirectoryLoader::kMaxEntries = 10'000
staticconstexpr

Hard cap on entries parsed per rescan (summed across every registered directory).

At 10k entries a rescan has already burned the 50 ms debounce budget many times over.

◆ kMaxFileBytes

constexpr qint64 PhosphorFsLoader::DirectoryLoader::kMaxFileBytes = 1 * 1024 * 1024
staticconstexpr

Per-file size cap.

Files larger than this are skipped with a warning — guards the GUI thread against a pathological user JSON. 1 MiB is far above any legitimate curve / profile / layout schema in this library's ecosystem.


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