pub struct SettingsFile<T: Versioned + DeserializeOwned> { /* private fields */ }Expand description
A reactive handle to a single typed file on disk.
Clone is cheap (an Rc bump). All clones share one in-memory
projection and one I/O thread.
Implementations§
Source§impl<T> SettingsFile<T>
impl<T> SettingsFile<T>
Sourcepub fn load(
path: PathBuf,
migrator: Migrator<T>,
) -> Result<Self, SettingsFileError>
pub fn load( path: PathBuf, migrator: Migrator<T>, ) -> Result<Self, SettingsFileError>
Load the file from disk (running migrations) or initialize with
T::default() if the file does not exist.
The initial read is lock-protected, exactly like every subsequent
mutate / replace: a peer that is mid-write when this process
starts up cannot hand us a torn read.
migrator is taken by value and retained for the lifetime of
the handle: every later locked read re-runs it, since a peer might
still be on an older on-disk schema at any point, not just at
startup.
On a genuine parse failure (the bytes are not valid TOML at all,
surviving MAX_READ_ATTEMPTS retries) the offending file is
renamed to <path>.broken-<ts> and the returned SettingsFile
starts from T::default() — the file really is corrupt, and the
quarantine lets the next launch start clean instead of repeatedly
failing to load it.
A SettingsFileError::Migrate or SettingsFileError::Io
failure, by contrast, is not quarantined:
Migratemeans the TOML parsed fine, but this build’s ownMigratorchain doesn’t know how to bring it up toT::CURRENT_VERSION— the classic symptom of an older build opening a file a newer peer process already wrote in a newer schema. The file is not corrupt; renaming it would destroy that peer’s live, legitimate, still-in-use data.Iomeans we couldn’t even read the file (permissions, a transient failure) — we never saw its content, so there is no basis at all for deciding it’s corrupt, and renaming (itself another I/O operation, on a path we just failed to read) would be reckless.
In both of those cases the handle falls back to T::default() for
this session only, but the file on disk is left completely
untouched. Use load_strict in tests that
want to assert on the specific failure instead.
Sourcepub fn load_strict(
path: PathBuf,
migrator: Migrator<T>,
) -> Result<Self, SettingsFileError>
pub fn load_strict( path: PathBuf, migrator: Migrator<T>, ) -> Result<Self, SettingsFileError>
Like load, but returns parse / migration errors
instead of quarantining the file. Intended for tests that want
to assert on a specific failure mode.
Sourcepub fn borrow(&self) -> Ref<'_, T>
pub fn borrow(&self) -> Ref<'_, T>
Borrow the current value. The returned Ref holds a RefCell
guard; do not call any mutating method on this SettingsFile
while a Ref is alive.
Sourcepub fn snapshot(&self) -> T
pub fn snapshot(&self) -> T
Clone the current value out. Convenient when you don’t want to juggle a borrow.
Sourcepub fn replace(&self, new: T) -> Result<(), SettingsFileError>
pub fn replace(&self, new: T) -> Result<(), SettingsFileError>
Replace the current value and persist it via a locked
read-modify-write. The disk read is discarded — replace always
wins over whatever was on disk — but the lock still serializes it
against a concurrent peer write, and the fresh disk stamp is
recorded so a subsequent reload doesn’t re-read our own write back
in as if it were new. T::set_version(T::CURRENT_VERSION) is called
so the version stamp is always coherent, even if the caller forgot.
Sourcepub fn mutate<F: FnOnce(&mut T)>(&self, f: F) -> Result<(), SettingsFileError>
pub fn mutate<F: FnOnce(&mut T)>(&self, f: F) -> Result<(), SettingsFileError>
Mutate the current value in place and persist it via a locked
read-modify-write: the file is re-read and re-migrated from disk
under an exclusive lock before f is applied, so f always sees
a fresh value — not this handle’s possibly-stale in-memory snapshot
— and the result is written back atomically before the lock is
released.
Takes f as FnOnce (not Fn) and imposes no Send bound on T:
this write is synchronous on the calling thread, never replayed on a
background worker, so there is no reason to tax every call site with
a Send/Fn requirement it doesn’t need.
Sourcepub fn reload_if_stale(&self) -> Result<bool, SettingsFileError>
pub fn reload_if_stale(&self) -> Result<bool, SettingsFileError>
Pick up a peer’s change: if the on-disk (mtime, len) differs from
the last one this handle observed, re-read and re-migrate the file
and refresh current. Returns whether a reload happened.
This is the cheap public probe — a stat, safe to call
speculatively (e.g. on every focus-in, or on a timer). It does not
perform the content-equality backstop that
Reloadable::reload_from_disk adds on top (which additionally
requires T: PartialEq); use that when a value-level “did anything
actually change” guarantee is needed (e.g. driven by a file
watcher, where a coincident stamp match must never be relied on
alone).
Sourcepub fn flush_now(&self) -> Result<(), SettingsFileError>
pub fn flush_now(&self) -> Result<(), SettingsFileError>
Synchronously write any pending payload to disk. A genuine no-op:
mutate / replace already write synchronously on the calling
thread, so nothing is ever pending — this type never registers
with the shared debounced-write worker pool at all, so there is
nothing to flush and nothing that can fail. Kept so callers that
hold a SettingsFile alongside debounced types (SettingsStore,
PersistedListModel) can flush everything uniformly without
special-casing this type.
Trait Implementations§
Source§impl<T: Versioned + DeserializeOwned> Clone for SettingsFile<T>
impl<T: Versioned + DeserializeOwned> Clone for SettingsFile<T>
Source§impl<T: Versioned + DeserializeOwned + Debug> Debug for SettingsFile<T>
impl<T: Versioned + DeserializeOwned + Debug> Debug for SettingsFile<T>
Source§impl<T> Reloadable for SettingsFile<T>
The content-equality backstop on top of SettingsFile::reload_if_stale
— see reload.rs’s module docs for the two-layer contract. Requires
T: PartialEq (only for this impl block; every other SettingsFile
method is unaffected), since this is the only place that needs to ask
“is the freshly-read value actually different from what’s live.”
impl<T> Reloadable for SettingsFile<T>
The content-equality backstop on top of SettingsFile::reload_if_stale
— see reload.rs’s module docs for the two-layer contract. Requires
T: PartialEq (only for this impl block; every other SettingsFile
method is unaffected), since this is the only place that needs to ask
“is the freshly-read value actually different from what’s live.”
Source§fn path(&self) -> &Path
fn path(&self) -> &Path
Reloadable handle.