Repository navigation
fix(persistence): never overwrite an unreadable data.json or write during window unload - #376
Merged
Merged
Conversation
…he window unloads (#370)
…ring window unload PodNotes treated a data.json it could not read like a missing one and saved defaults over it. A reload during playback produced such a file: the player's hide save started a write after pagehide, and the page unloaded after the write truncated data.json. An unreadable data.json is now left unchanged. PodNotes shows a Notice, runs on defaults and saves nothing for the session. From pagehide or Obsidian's quit on, every data.json write is refused, and strict saves report the refusal. Quit waits for a write already in flight. Fixes #370
Deploying podnotes with
|
| Latest commit: |
4f0b1f7
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://7b1c1cf1.podnotes.pages.dev |
| Branch Preview URL: | https://fix-unreadable-data-json.podnotes.pages.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reloading Obsidian while an episode plays no longer resets PodNotes to defaults, and PodNotes never saves over a
data.jsonit could not read.Fixes #370
The cause had two parts, both traced in real Obsidian:
pagehidefires and then the player'svisibilitychangehandler saves the position. That write truncatesdata.json, and the page unloads before the write finishes, so the file is left at 0 bytes.loadDatareturns nothing for a file it cannot parse. PodNotes treated that like a new install and saved defaults over the file. It also deleted every private feed URL from SecretStorage, because the defaults reference none.What changed:
data.jsonexists but cannot be read, PodNotes leaves it unchanged. It shows a sticky Notice with the file path, runs on default settings, and saves nothing for the session. Strict saves (import, API key selection) reject.pagehideor Obsidian'squiton, everydata.jsonwrite is refused. One write boundary covers the save loop and the startup migration writes.Scope: Obsidian's file adapter has no atomic replace (
adapter.renamerefuses an existing destination). So a reload that lands during an in-flight periodic save can still truncate the file. Here a 1 MB write takes about 19 ms (median), with one save about every 4 s of playback. Atomic writes would need Nodefson desktop, so they are left for a follow-up.Verification
Runtime: Obsidian 1.13.7 (installer 1.13.7), Linux, headless isolated worktree instance. Feed: the verify-skill fixture
PodNotes Fixture(http://127.0.0.1.nip.io:32735/feed.xml),Fixture Episode 1(60 s WAV), added through Settings and played from the episode list.pagehide, thensaveDatafromsetEpisodeTimeviarunSaveLoop, then awriteFilestart with no finish beforeunload.data.jsonwent from 3799 to 1960 bytes, with no feeds and no history.pagehide.data.jsonkept its feeds and its byte count.data.jsonto half its size while Obsidian was stopped and then started it. Within 5 s PodNotes replaced the file with defaults, with no Notice.quitfired with no write in flight and no task was added. The window closed in 387 ms, no write started afterwards, anddata.jsonkept its feeds.keeps data.json when Obsidian reloads while an episode playsandleaves a data.json it cannot read unchanged and saves nothingboth fail on master code, 2 of 2 runs. Both pass in all 6 full runs ofnpm run test:e2e(21 tests) across the review rounds, including the head commit.pagehidewiring, and thequitwiring (no task when idle, a task that waits for the write in flight and never rejects). Each fails on mastermain.ts.Checks on the head commit (Node 22.23.2):
npm run lint: passnpm run format:check: passnpm run typecheck: passnpm run build: passnpm run test: pass (Vitest 80 files, 1216 tests)npm run docs:build: pass, with mkdocs fromdocs/requirements.txtin a scratch venvRelease impact
Storage: no schema or format change. An unreadable
data.jsonis now preserved, and that session runs read-only on defaults. Saves requested after the window starts unloading or Obsidian starts quitting are dropped. So the last position update before a reload or quit, the write that caused the loss, is not saved; the periodic save keeps the position within about 5 s. No API or URI changes.Note
Never overwrite unreadable
data.jsonor write during window unload in PodNotesloadSettingsin src/main.ts now checks whetherdata.jsonexists whenloadDatareturns null. If the file exists but cannot be read or parsed, the plugin keeps the file unchanged, resets settings to defaults, shows a persistent notice, and refuses all later writes.onloadregisters a windowpagehidehandler and an Obsidian quit handler that refuse new writes. The quit handler also registers the active write as a quit task so Obsidian waits for an in-flight save to settle.writeDataFileguard, which rejects refused writes and tracks the in-flight promise.data.json.pagehideor an unreadabledata.json) instead of silently succeeding; callers that don't catch these rejections will see rejected promises (a catch handler is attached internally to avoid unhandled rejections).Macroscope summarized 4f0b1f7.