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:
@@ -14,7 +14,7 @@ use crate::application::dtos::display_helpers::{
|
||||
};
|
||||
use crate::application::dtos::file_dto::FileDto;
|
||||
use crate::application::dtos::folder_dto::FolderDto;
|
||||
use crate::application::ports::external_mount_ports::MountStat;
|
||||
use crate::application::ports::external_mount_ports::{MountEntry, MountStat};
|
||||
use crate::application::services::mount_registry::MountConfig;
|
||||
use crate::domain::services::external_mount_id::{
|
||||
encode_child_id, virtual_file_etag, virtual_folder_etag,
|
||||
@@ -71,6 +71,56 @@ pub fn mount_folder_dto(cfg: &MountConfig, parent_id: &str, stat: &MountStat) ->
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a `FolderDto` from a directory listing entry. `parent_id` is the
|
||||
/// id-string of the directory being listed.
|
||||
pub fn mount_entry_folder_dto(cfg: &MountConfig, parent_id: &str, entry: &MountEntry) -> FolderDto {
|
||||
FolderDto {
|
||||
etag: virtual_folder_etag(entry.modified_at),
|
||||
id: encode_child_id(cfg.mount_id, entry.node_id.clone()),
|
||||
name: entry.name.clone(),
|
||||
path: String::new(),
|
||||
parent_id: Some(parent_id.to_owned()),
|
||||
owner_id: Some(cfg.owner_id.to_string()),
|
||||
drive_id: cfg.drive_id,
|
||||
created_at: entry.created_at,
|
||||
modified_at: entry.modified_at,
|
||||
is_root: false,
|
||||
icon_class: Arc::from("fas fa-folder"),
|
||||
icon_special_class: Arc::from("folder-icon"),
|
||||
category: Arc::from("Folder"),
|
||||
created_by: None,
|
||||
updated_by: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a `FileDto` from a directory listing entry (mime sniffed from name).
|
||||
pub fn mount_entry_file_dto(cfg: &MountConfig, parent_id: &str, entry: &MountEntry) -> FileDto {
|
||||
let name = entry.name.as_str();
|
||||
let mime = mime_guess::from_path(name)
|
||||
.first_or_octet_stream()
|
||||
.to_string();
|
||||
FileDto {
|
||||
id: encode_child_id(cfg.mount_id, entry.node_id.clone()),
|
||||
name: name.to_owned(),
|
||||
path: String::new(),
|
||||
size: entry.size,
|
||||
mime_type: Arc::from(mime.as_str()),
|
||||
folder_id: Some(parent_id.to_owned()),
|
||||
created_at: entry.created_at,
|
||||
modified_at: entry.modified_at,
|
||||
icon_class: Arc::from(icon_class_for(name, &mime)),
|
||||
icon_special_class: Arc::from(icon_special_class_for(name, &mime)),
|
||||
category: Arc::from(category_for(name, &mime)),
|
||||
size_formatted: format_file_size(entry.size),
|
||||
owner_id: Some(cfg.owner_id.to_string()),
|
||||
sort_date: None,
|
||||
content_hash: String::new(),
|
||||
etag: virtual_file_etag(entry.size, entry.modified_at),
|
||||
created_by: None,
|
||||
updated_by: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a `FileDto` for a mount file from its stat. Virtual files have no blob
|
||||
/// hash (`content_hash` empty) and a size+mtime etag.
|
||||
pub fn mount_file_dto(cfg: &MountConfig, parent_id: &str, stat: &MountStat) -> FileDto {
|
||||
|
||||
Reference in New Issue
Block a user