A copy of an invalid settings file that cannot be written, for example
on a full disk, is now reported and the save goes ahead, as it does
when the copy cannot be given its name. Before, the save was refused.
On a volume that needs the fallback move, a read-only settings file is
recognized by its attribute and refused at once instead of after the
full wait.
Refs #46
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The rename that replaces the settings file now takes the path in the
form Windows reads as written, so a data folder past 260 characters,
or one whose name is not well-formed UTF-16, saves like any other.
Before, every save after the first was refused there.
The copy of an invalid file is written to a temporary name and given
its kept-aside name only after the save succeeds, so a failed save can
no longer overwrite and then delete an earlier copy, and a killed save
leaves no duplicate copies. A file over 16 MiB is replaced without a
copy.
On a volume that needs the fallback move, a reader's open handle is
waited out instead of failing the save at once.
Refs #46
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The stored file is now replaced with a single rename that works while
other handles are open on it, so there is never a moment without a
settings file and a process killed mid-save leaves the old file or the
new one. Volumes without that rename fall back to a move.
Content the JSON reader cannot decode is treated as invalid content
rather than an error, the read handle is released on every path, and a
kept-aside copy that cannot be made no longer blocks the save. Clearing
recent files also deletes kept-aside copies.
ADR-0006, the user guide and the threat model describe the protocol as
implemented.
Refs #46
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
Reading while denying writers makes a file that is being rewritten in
place read as busy instead of as invalid content, and the read handle is
held until the swap, so the content judged is the content replaced. The
swap uses a replace operation: an invalid original moves to its kept-aside
name in the same step, a failed save leaves it in place, and a reader that
allows replacement no longer blocks a save.
Access denied is permanent and reported at once; only a sharing violation
is waited out, so a read-only profile no longer stalls a window. One
invalid value costs only that value, a trailing comma, a comment and a
quoted number are accepted, and a saved theme the JSON reader cannot
decode is discarded instead of stopping startup. Kept-aside files use the
first free of nine names. Only this store's own temporary files are
removed. ADR-0006 records the on-disk protocol.
Tests cover each of these with real handles held the way other programs
hold them, a deterministic interleaving of two instances, the on-disk
format against the previous store, and bounded retries in the concurrent
test.
Refs #46
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
A load that hit a sharing violation returned defaults, and the next save
wrote them back over the stored themes, CSS, editors and recent files. Two
processes over one data folder also erased each other's changes: each did
load, modify, save with no lock and one shared temporary file name.
AppSettingsStore applies one change to the latest stored settings under a
lock file beside settings.json and replaces the file by rename. A file that
cannot be read after bounded retries is never overwritten; only invalid
content counts as corruption and is kept as settings.json.corrupt. A file
over 16 MiB is not loaded, abandoned temporary files are removed, and
faults are logged by kind, exception type and HRESULT only.
Every save in the app now goes through Update. Tests use real exclusive
handles and concurrent instances and count attempts instead of time.
Closes#46
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>