Watcher + debounce + rescan scaffolding for filesystem-backed loaders. More...
#include <phosphor-fsloader/include/PhosphorFsLoader/WatchedDirectorySet.h>
Signals | |
| void | rescanCompleted () |
| Fired after every rescan, coalesced by the 50 ms debounce. | |
Public Member Functions | |
| WatchedDirectorySet (IScanStrategy &strategy, QObject *parent=nullptr) | |
| Construct with a borrowed strategy. | |
| ~WatchedDirectorySet () override | |
| WatchedDirectorySet (const WatchedDirectorySet &)=delete | |
| WatchedDirectorySet & | operator= (const WatchedDirectorySet &)=delete |
| int | registerDirectory (const QString &directory, LiveReload liveReload=LiveReload::Off) |
| Register a directory for scanning + (optionally) watching. | |
| int | registerDirectories (const QStringList &directories, LiveReload liveReload=LiveReload::Off, RegistrationOrder order=RegistrationOrder::LowestPriorityFirst) |
| Register multiple directories in caller-declared priority order. | |
| int | setDirectories (const QStringList &directories, LiveReload liveReload=LiveReload::Off, RegistrationOrder order=RegistrationOrder::LowestPriorityFirst) |
Replace the registered directory set with directories. | |
| void | requestRescan () |
| Trigger a debounced rescan of every registered directory. | |
| void | rescanNow () |
| Run a rescan synchronously on the calling stack. | |
| QStringList | directories () const |
| Currently-registered directories in registration order. | |
| void | setDebounceIntervalForTest (int ms) |
| Test-only: override the debounce interval (default 50 ms). | |
| QString | watchedAncestorForTest (const QString &target) const |
Test-only: introspection of the ancestor-watch bookkeeping used by attachWatcherForDir when a target directory does not yet exist. | |
| bool | hasParentWatchForTest (const QString &path) const |
Test-only: returns true if path is currently in the internal parent-watch set. | |
Static Public Member Functions | |
| static QStringList | filterNewSearchPaths (const QStringList &candidates, const QStringList &alreadyRegistered) |
Helper for consumers building search-path APIs on top of the set: canonicalises (QDir::cleanPath) every entry in candidates, drops empties, and returns the subset that is NOT already in alreadyRegistered (and is not duplicated within candidates itself). | |
Watcher + debounce + rescan scaffolding for filesystem-backed loaders.
Owns the cross-cutting plumbing every "scan a set of directories, register entries, watch for changes, rescan on edit" loader needs:
• Registration of one or more directories, in caller-chosen order. • QFileSystemWatcher-backed live reload with a 50 ms single-shot debounce that coalesces the editor save-temp-rename dance into one rescan. • Parent-directory watching when the target doesn't exist yet (fresh-install consumers that create their data dir on first write still get hot-reload — the base notices when the target materialises and promotes to a direct watch). • Re-arming individual file watches after every rescan, so editors that save via atomic rename (the new inode is a different watch candidate) still fire on the next edit. • A rescan-during-rescan race guard that ensures a watcher event delivered while the scan is in progress is replayed afterwards instead of being silently dropped.
Schema-specific concerns (file extension, parsing, registry commits, user-wins layering) live in the consumer-supplied IScanStrategy.
GUI-thread only. The watcher and the debounce timer live on the thread the set was constructed on. Call every public method from the same thread.
|
explicit |
Construct with a borrowed strategy.
strategy must outlive the set. Taken by reference rather than raw pointer so there is no need for a null-check at the call site; "strategy is always valid" is a compile-time guarantee.
|
override |
|
delete |
| QStringList PhosphorFsLoader::WatchedDirectorySet::directories | ( | ) | const |
Currently-registered directories in registration order.
Intended to be called on the GUI thread. The returned QStringList is a by-value, implicitly-shared snapshot — once obtained on the GUI thread it can be propagated to worker threads safely (e.g. captured into a QtConcurrent::run lambda for the shader-warming path). Calling this method concurrently with a mutating call on the GUI thread (registerDirectories, setDirectories, requestRescan, rescanNow) is a data race on the underlying QString refcounts; snapshot first, share later.
|
static |
Helper for consumers building search-path APIs on top of the set: canonicalises (QDir::cleanPath) every entry in candidates, drops empties, and returns the subset that is NOT already in alreadyRegistered (and is not duplicated within candidates itself).
Preserves caller order.
Both addSearchPaths-style entry points in PhosphorShaders::ShaderRegistry and PhosphorAnimationShaders::AnimationShaderRegistry use this to skip log-line spam when the same path is registered twice (the base's own registerDirectories is silent on dedup, so the log has to be filtered upstream).
| bool PhosphorFsLoader::WatchedDirectorySet::hasParentWatchForTest | ( | const QString & | path | ) | const |
Test-only: returns true if path is currently in the internal parent-watch set.
|
delete |
| int PhosphorFsLoader::WatchedDirectorySet::registerDirectories | ( | const QStringList & | directories, |
| LiveReload | liveReload = LiveReload::Off, |
||
| RegistrationOrder | order = RegistrationOrder::LowestPriorityFirst |
||
| ) |
Register multiple directories in caller-declared priority order.
The base normalises directories into the canonical [lowest-priority, ..., highest-priority] shape before storing — see RegistrationOrder for the two accepted input forms. The stored list is what directories() returns and what the strategy sees on every rescan, so all four in-tree strategies can rely on the same iteration shape regardless of which form the caller used.
Same set-wide one-way liveReload semantics as registerDirectory.
| int PhosphorFsLoader::WatchedDirectorySet::registerDirectory | ( | 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. Triggers an immediate synchronous rescan via the strategy.
Single-path registration carries no priority direction (one entry has nothing to be priority-ordered against), so no RegistrationOrder parameter is needed.
liveReload is a set-wide one-way enable: once any call passes LiveReload::On, the watcher is created and persists for the rest of the set's lifetime. Subsequent LiveReload::Off calls do not skip watching for the new directory — the per-rescan re-attach loop (which exists to recover atomic-rename inode swaps) installs a watch for every directory in m_directories regardless of how it was registered. The flag's effect is therefore limited to the immediate post-registration window: an Off registration after a prior On call won't have a watch until the next rescan completes, but it will from then on. Callers that need to stop watching entirely should destroy and rebuild the set.
The default is Off because this is the library primitive — tests, batch imports, and explicit-refresh consumers compose against it. Higher-level consumer wrappers (PhosphorShaders::ShaderRegistry, PhosphorAnimationShaders::AnimationShaderRegistry, ScriptedAlgorithmLoader) flip the default to On because their production callers always want hot-reload — that override is the documented convention, not a drift.
| void PhosphorFsLoader::WatchedDirectorySet::requestRescan | ( | ) |
Trigger a debounced rescan of every registered directory.
Safe to call multiple times in rapid succession — the single-shot debounce timer collapses into one fire. Consumers wire this into their own cross-process notification channels (D-Bus signal on config rewrite, etc.) to cover QFileSystemWatcher's known atomic-rename blind spots.
If a rescan is already in progress on the calling thread (e.g. the strategy's commit step re-entered us via a signal connection), the request is replayed at the end of the running rescan instead of being silently dropped.
|
signal |
Fired after every rescan, coalesced by the 50 ms debounce.
Emitted unconditionally on rescan completion — the strategy's commit step has already touched the consumer's registry by the time this fires.
| void PhosphorFsLoader::WatchedDirectorySet::rescanNow | ( | ) |
Run a rescan synchronously on the calling stack.
Bypasses the debounce timer — useful for Q_INVOKABLE "refresh now" entry points that need the strategy's commit step to land before the caller returns.
Safe to call regardless of live-reload state. If a debounced rescan is pending it is cancelled (the synchronous run covers the same ground).
| void PhosphorFsLoader::WatchedDirectorySet::setDebounceIntervalForTest | ( | int | ms | ) |
Test-only: override the debounce interval (default 50 ms).
Production code MUST NOT call this — the 50 ms debounce is a hard requirement for collapsing the 2-3 event save-temp-rename dance every editor performs.
| int PhosphorFsLoader::WatchedDirectorySet::setDirectories | ( | const QStringList & | directories, |
| LiveReload | liveReload = LiveReload::Off, |
||
| RegistrationOrder | order = RegistrationOrder::LowestPriorityFirst |
||
| ) |
Replace the registered directory set with directories.
Unlike registerDirectories (append-only), this is a full replacement: directories present in the current set but not in directories are dropped, their direct watches are removed, and any ancestor proxy watches they alone depended on are released. Directories present in both sets are preserved (no churn). Directories new to the set are appended in directories' order.
Use this when the underlying source-of-truth for the directory list is dynamic (e.g. XDG paths that change as packages install / uninstall) and the consumer needs to drop stale entries cleanly.
The strategy is invoked synchronously with the post-replacement directory list. Per-file watches are re-synced from the strategy's return value, so files belonging only to dropped directories are cleaned up on the same call.
Same set-wide one-way liveReload semantics as registerDirectory: passing On enables the watcher (if not already enabled), passing Off does not disarm it.
Same RegistrationOrder semantics as registerDirectories — directories is normalised into the canonical [lowest-priority, ..., highest-priority] shape before being stored and passed to the strategy.
| QString PhosphorFsLoader::WatchedDirectorySet::watchedAncestorForTest | ( | const QString & | target | ) | const |
Test-only: introspection of the ancestor-watch bookkeeping used by attachWatcherForDir when a target directory does not yet exist.
Returns the actually-watched ancestor for target, or an empty string if no such ancestor watch is recorded.