From 599715a447dd61544ae3401cdd2ad689fef5c002 Mon Sep 17 00:00:00 2001 From: claudio Date: Mon, 11 May 2026 13:19:05 +0200 Subject: [PATCH] 120 mule-images: folder rename round-trip both ways - handle_directory_rename in scan.py covers NC-side renames via the webhook. Iterates rows in Python (asyncpg int-type quirk on raw SUBSTRING+LENGTH). - Existing PATCH /folders/{id} handles mule-side renames; webhook feedback hits the same helper and is a 0-row no-op (idempotent). --- containers/120-mule-images.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/containers/120-mule-images.md b/containers/120-mule-images.md index ef4b793..39dfa41 100644 --- a/containers/120-mule-images.md +++ b/containers/120-mule-images.md @@ -113,7 +113,12 @@ Verified end-to-end on `Photos/MuleTestFolder/{test1,test2,nested/test3}.jpg`: ` **Known remaining gap — trashbin restore.** Moving a file out of `/dav/trashbin/.../trash/foo.jpg.dXXXX` back to `/files/...` via WebDAV MOVE fires **no event mule subscribes to**. NC's trashbin app emits its own internal event class that isn't in the `OCP\Files\Events\Node\*` set we registered with `webhook_listeners`. Workaround: re-upload via PUT (covered by the resurrect-on-rewrite fix above) or wait for the 30-min reconcile sweep. -**Also known — folder rename.** Like folder delete pre-patch, a renamed directory fires one `NodeRenamedEvent` on the folder itself; the handler ignores it because the path has no supported extension. Children retain stale `filepath` rows. Fix would mirror the directory-delete approach: `UPDATE photos SET filepath = REPLACE(filepath, old_prefix, new_prefix) WHERE filepath LIKE old_prefix||'/%'`. Not yet implemented. +**Folder rename round-trip (commits `f4a03b6` + `f27f3cb`).** Both directions now work: + +- *NC-side rename → mule:* webhook's "renamed" branch detects directory rename (neither path has a supported extension) and calls new `handle_directory_rename(old, new)` in `scan.py`. The helper iterates matching rows in Python and prefix-rewrites `Photo.filepath`, `Folder.path`, `SourceRoot.path` in one transaction. Cross-source-root case (rare) discards the old subtree and lets scan_folder add fresh rows under the new root. +- *Mule-side rename → NC:* the existing `PATCH /api/v1/folders/{id}` endpoint (`folders.py:202`) already does WebDAV MOVE via `nextcloud_dav.move_for_user` and rewrites mule's DB inline. The NodeRenamedEvent that bounces back through the webhook hits `handle_directory_rename`, which finds 0 rows under the old prefix and is a no-op — feedback loop is idempotent. + +**asyncpg gotcha**: the original implementation of `handle_directory_rename` used raw SQL with `SUBSTRING(filepath FROM LENGTH(:old_prefix) + 1)` so the offset would be computed server-side. asyncpg's type inference miscategorises the `LENGTH()` result and rejects the parameter as "$2: int (expected str)". The fix iterates in Python (same pattern as the existing PATCH endpoint). Lesson: avoid passing `LENGTH(:x)+1` as an argument to `SUBSTRING(...)` via asyncpg + sqlalchemy `text()`. **Open: Phase 3.** Enable the Nextcloud Memories app (currently installed but disabled) and read `taken_at` / `lat` / `lon` / `w` / `h` from `oc_memories` instead of running mule's own `extract_metadata`. Keep pHash + file_hash for dedup. When that lands, the vision worker can also stop needing on-disk medium thumbs and the whole `/data/thumbs` pipeline retires.