Make WebDAV PUT idempotent via auto-materialisation of missing parents#58
Conversation
PUT requests now auto-materialise missing notebook/note collections from their slugs (id-prefix) so a partial MKCOL chain or a slug-encoding mismatch no longer aborts the upload. PROPFIND name comparison is also URL-decoded client-side so notes with non-ASCII titles (e.g. 'Chaudré', 'Commande d'imprimante…') are recognised as already existing. https://claude.ai/code/session_01LsksuvVsUfJgTExHMXYUuw
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a931cbe231
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if auto_materialize and len(parts) >= 3: | ||
| target_note = _materialize_note(self.db, target_nb, parts[1]) |
There was a problem hiding this comment.
Defer parent auto-creation until PUT target is validated
get_resource_inst() now creates missing notebook/note collections for every PUT path with at least three segments, before confirming the child resource is actually writable (e.g., note.json or page_N.ink) and before payload validation runs. With WsgiDAV’s do_PUT flow, a request like PUT /nb__deadbeef/note__cafebabe/unsupported.bin (or malformed note.json) will still persist a new notebook/note, then fail with 403/400, leaving orphan data from a failed write. This is a regression in data integrity because failed requests now mutate storage.
Useful? React with 👍 / 👎.
Summary
This PR fixes a critical issue where sync push operations would fail with 409 errors when notes had non-ASCII characters in their titles (e.g., "Chaudré de saucisses", "Commande d'imprimante"). The root cause was that URL-encoded slugs in PROPFIND responses weren't being decoded, causing the client to think parent collections didn't exist and triggering redundant MKCOL requests.
The fix implements two complementary strategies:
Auto-materialisation of missing parents: When a PUT request targets a path whose ancestors don't exist, the server now transparently creates the missing notebook and note from their slugs (if they carry valid id-prefixes). This makes PUT idempotent and eliminates spurious 409 failures.
URL decoding in PROPFIND parsing: The client now properly decodes URL-encoded hrefs returned by PROPFIND, so notes with accented characters are correctly recognized as already existing.
Key Changes
Server-side (webdav_provider.py)
get_resource_inst()to auto-materialize missing parent collections when handling PUT requests_materialize_notebook()helper to create notebooks from slugs with valid id-prefixes_materialize_note()helper to create notes with placeholder ids from slugsClient-side (client.py)
_parse_propfind()to URL-decode href paths before extracting resource namesTest coverage (test_webdav_sync_push.py)
test_put_into_missing_parent_returns_409_not_500totest_put_into_missing_parent_auto_materialiseswith full validationtest_put_with_no_id_prefix_still_failsto ensure 409 is still returned for invalid slugsTestIdempotentPushclass with 5 new tests covering:Implementation Details
https://claude.ai/code/session_01LsksuvVsUfJgTExHMXYUuw