feat(mounts): P3 — WebDAV/NextCloud path resolution + browse/download
Mounts are now browsable and downloadable over both WebDAV surfaces (/webdav/ and NextCloud /remote.php/dav) and NextCloud clients. The work funnels through the path-based service methods all three surfaces already use, so both WebDAV handlers gain mount support with no handler changes. - get_folder_by_path / get_file_by_path resolve a path that descends past a mount root to a synthetic ext: DTO (the mount root itself stays a real row) - list_folders_paginated_with_perms / list_files_batch_with_perms branch to the provider, so PROPFIND Depth:1 enumerates mount directory contents in both WebDAV handlers - get_file_stream / get_file_range_stream branch ext: file ids to the provider, so WebDAV GET streams mount content (Pin<Box<dyn Stream>> re-boxed) - router injected into FileRetrievalService; new MountRouter::find_path delegates to the registry's (drive_id, mount_path) index - WebDAV mkdir/delete/move/rename by path work via path→ext:id→the P2 service methods (no extra wiring) Fixes a leading-slash normalization bug in the registry path index: materialized folder paths arrive both as `Personal/Media` and `/Personal/Media`; keys and lookups now normalize the leading slash (would have broken real WebDAV paths). Known follow-up: WebDAV PUT (upload/update by path) still ingests to the CAS; streaming a WebDAV PUT straight to the provider needs a pre-ingest branch in the two WebDAV PUT handlers (mirrors the REST upload branch). Integration test: get_folder_by_path/get_file_by_path resolution, PROPFIND Depth:1 folder+file listing, and content streaming on a real mount + Postgres.
This commit is contained in:
@@ -90,6 +90,11 @@ impl MountRegistry {
|
||||
internal_path: &str,
|
||||
) -> Option<(Arc<MountConfig>, String)> {
|
||||
let index = self.inner.load();
|
||||
// Normalize away a leading slash: materialized folder paths arrive both
|
||||
// as `Personal/Media` (raw `folders.path`) and `/Personal/Media`
|
||||
// (FolderDto / WebDAV internal paths). The index keys are stored without
|
||||
// a leading slash (see `reload`).
|
||||
let internal_path = internal_path.trim_start_matches('/');
|
||||
// Walk ancestor paths from the full path up to the root, longest first,
|
||||
// so the deepest matching mount wins.
|
||||
let mut candidate = internal_path;
|
||||
@@ -144,7 +149,15 @@ impl MountRegistry {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
by_path.insert((rec.drive_id, rec.mount_path.clone()), rec.mount_folder_id);
|
||||
// Store the path key without a leading slash so lookups normalize
|
||||
// consistently (see `find_mount_for_path`).
|
||||
by_path.insert(
|
||||
(
|
||||
rec.drive_id,
|
||||
rec.mount_path.trim_start_matches('/').to_string(),
|
||||
),
|
||||
rec.mount_folder_id,
|
||||
);
|
||||
by_folder.insert(
|
||||
rec.mount_folder_id,
|
||||
Arc::new(MountConfig {
|
||||
|
||||
Reference in New Issue
Block a user