Skip to content

Preserve creation timestamps and chunked modification times - #69

Open
jiongxuan wants to merge 4 commits into
siosig:mainfrom
jiongxuan:fix/preserve-file-times
Open

jiongxuan wants to merge 4 commits into
siosig:mainfrom
jiongxuan:fix/preserve-file-times

Conversation

@jiongxuan

@jiongxuan jiongxuan commented Oct 18, 2026 •

Copy link
Copy Markdown

A file downloaded from Nextcloud currently loses its original creation time on macOS, and chunked uploads also lose the supplied modification time at the final assembly MOVE. Identical-content fast paths can skip timestamp-only changes entirely.

This change carries DAV:creationdate through listings and state, sends X-OC-CTime on ordinary and chunked uploads, and applies Obsidian's native ctime/mtime write options to atomic writes and open-note updates. Existing local creation times survive ordinary rewrites. Chunk assembly now carries both timestamp headers. When creation time is later than modification time, Obsidian 1.14.4's native write order can silently clamp birthtime on macOS/iOS. The adapter feature-detects the existing native timestamp setters, restores creation time last, and verifies both values before reporting success.

For identical content, each timestamp reconciles against its last converged local/remote baseline. Local changes use an ETag-guarded PROPPATCH with Nextcloud's writable DAV:lastmodified property and a new file ETag to invalidate directory caches. Remote changes restore local timestamps. Concurrent edits to the same timestamp, a content identity race, partial property failures, and ignored server updates fail without advancing the baseline. With no timestamp baseline, the existing server timestamp wins. Large first-sync files and checksum-less body comparisons use the same timestamp reconciliation before advancing state. Comparisons use whole-second precision; listings without creation dates retain legacy behavior.

Binary transfer and identity paths pass bytes to the merge-base recorder, which checks file type before decoding. Previously, even videos were decoded into a discarded string; a binary attachment above the V8 string limit could fail an otherwise successful sync. Markdown and configured text merge bases retain their existing behavior.

The desktop scanner also visits visible directories to find their hidden children, while retaining cached stats for ordinary files and respecting exclusions. Without this, a file such as Projects/assets/.backup is absent from the upload scan. Mobile keeps its previous traversal behavior.

Validation:

  • TypeScript check and production build passed. 162 unit suites / 1,790 tests passed. The existing sliderLimits.test.ts was excluded because its generated specs/main/desktop/settings.html fixture is absent from this checkout; no replacement fixture was fabricated.

  • Tested the actual production bundle inside Obsidian 1.14.4 on macOS, against Nextcloud 35.0.1, using temporary files in two existing Vaults with the desktop File Provider paused. Each Vault passed content SHA-256, native APFS birthtime and mtime comparisons for ordinary files, zero-byte files, 52 MiB chunked attachments, nested hidden files, edits in both directions, independent ctime-only and mtime-only changes in both directions, remote rename, an open-note reverse edit, and deletion cleanup.

  • The identical-conflict regression tests reproduced five failures before the follow-up fix and passed after it. The updated production bundle also passed SHA-256 and native timestamp checks for a 21 MiB identical first-sync file and an identical first-sync file with checksums suppressed in each of the same two existing Vaults, with no content re-transfer. Mobile adapter contract tests cover write options, preservation during rewrites, and explicit failure when a native adapter ignores creation times.

  • Binary merge-base regression tests fail on the previous implementation and pass with lazy decoding. The new production bundle was also exercised in each existing Vault with a decoder guard that would throw on any attempted conversion of the 21 MiB binary attachment; both runs preserved SHA-256 and native times, with zero binary decoding calls.

  • The unmodified 1.0.11 bundle reproduced the original problem on a temporary file: content and mtime matched the server, while native birthtime did not. Tests exercise real WebDAV writes, including root-ETag invalidation after metadata changes.

  • On a physical iPhone running iOS 27.2 and Obsidian 1.14.4, the production bundle passed SHA-256 and native filesystem birthtime/mtime read-back for Markdown, an empty file and a 512 KiB binary attachment, edits in both directions, independent creation-only and modification-only updates in both directions, an actual editor change, rename and deletion. An additional creation-later-than-modification case reproduced the silent clamp before this follow-up fix, then passed for a fresh download, repair of an existing file, and an open-note update whose modification time moved further backwards. The native desktop adapter passed the same ordering case in two existing Vaults. All probes used disposable files inside existing Vaults.

Windows, Linux and Android native timestamp restoration have not been integration-tested here. No credentials, personal documents or private environment logs are included in this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant