Commit Graph
4 Commits
Author SHA1 Message Date
Cursor Agentandrmcrackan c11268c6b4 fix(test): clear CA1416 platform warnings on the unix file mode calls
The platform compatibility analyzer flagged all four File.GetUnixFileMode /
SetUnixFileMode calls as reachable on Windows. Assert.Inconclusive is not
[DoesNotReturn], so the OperatingSystem.IsLinux/IsMacOS check did not narrow the
platform for anything after it, and narrowing from such a check does not reach
inside a lambda at all, which left the Timer restore callback flagged regardless.

Return explicitly after the skip, and move the body into a method attributed
[SupportedOSPlatform("linux")]/[SupportedOSPlatform("macos")] so the callback
inherits that context. Both files I touch now build warning free.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-17 04:07:42 +00:00
Cursor Agentandrmcrackan bbb401ef87 fix(config): outlast a brief lock on Settings.json, and scope the reader test to unix
Windows CI caught the atomic replace failing with UnauthorizedAccessException:
renaming over a file is denied while another handle holds it open, however
generously that handle shares the file. In production the CLI, a second GUI
instance or a virus scanner can each hold Settings.json for a moment, so retry the
replace a few times before letting the caller see the failure. The previous
File.WriteAllText threw on the same holds, so this is strictly more forgiving.

ExternalReaderNeverSeesAPartiallyWrittenFile keeps a handle open almost
continuously, which no retry budget can outlast on Windows, so restrict it to unix
where it actually tests write atomicity. Write_SurvivesATemporarilyUnwritableDirectory
covers the retry instead by revoking write permission on the containing directory.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-17 03:47:54 +00:00
Cursor Agentandrmcrackan f472334f09 fix(config): replace Settings.json atomically instead of truncating it
File.WriteAllText truncates the destination before writing, so an interrupted
write leaves a half-written or empty Settings.json, and the in-process lock added
in the previous commit cannot help a reader in another process - the GUI and the
CLI share this file.

Route every write through Dinah.Core.IO.AtomicFileWriter, which writes a sibling
temp file, flushes to disk and renames it over the destination. Validate the temp
file parses as json before the swap, the same way JsonFilePersister<T> already
saves AccountsSettings.json, so a bad payload leaves the existing file untouched.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-17 02:23:17 +00:00
Cursor Agentandrmcrackan d0cf459450 fix(config): serialize PersistentDictionary cache and file access
Configuration.Instance is a process-wide singleton read and written from the UI
thread, BackgroundWorker callbacks and download workers at the same time, but
PersistentDictionary guarded only its file writes. Its two Dictionary caches were
touched without synchronization, so concurrent inserts eventually corrupted them
and threw "Operations that change non-concurrent collections must have exclusive
access" (issue #1959, reported from MainVM.UpdateCountsBw_Completed reading
AutoDownloadEpisodes while a second GetCounts pass ran).

Reads on the file were unguarded too: Exists/GetJObject could observe a partially
written Settings.json, and readFile responds to empty contents by rewriting the
file, so a reader racing a writer could scramble the settings on disk.

Take one lock across each operation's cache and file access, and keep logging
outside it by having writeFile report whether it rewrote the file.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-17 01:58:54 +00:00