Files
Oxicloud/src/application/services/mount_dto.rs
T
Edouard Vanbelle 53077e0fa3 fix(merge): fix merge of mount + is_favorite feature
fix the merge of moun feature and is_favorite + is_shared feature
    together it was raising an impossible build due to missing schema
2026-07-22 08:30:06 +02:00

157 lines
6.2 KiB
Rust

//! Builders that synthesize `FolderDto` / `FileDto` from a provider [`MountStat`].
//!
//! Mount entries have no `storage.folders`/`storage.files` row, so the normal
//! `FolderDto::from(Folder)` path doesn't apply. These helpers produce the same
//! DTO shape from a provider stat plus the mount config, with a synthetic `ext:`
//! id and a virtual etag. Shared by the folder/file services and the handlers.
use std::sync::Arc;
use uuid::Uuid;
use crate::application::dtos::display_helpers::{
category_for, format_file_size, icon_class_for, icon_special_class_for,
};
use crate::application::dtos::file_dto::FileDto;
use crate::application::dtos::folder_dto::FolderDto;
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,
};
/// Final path segment of a node id (the display name).
fn node_name(node_id: &str) -> &str {
node_id.rsplit('/').next().unwrap_or(node_id)
}
/// Emit the structured audit line for a mount mutation (per AGENTS.md). Every
/// write op (upload / mkdir / rename / delete / move) calls this.
pub fn audit_mount_write(action: &str, cfg: &MountConfig, caller_id: Uuid, node_id: &str) {
tracing::info!(
target: "audit",
event = "external_mount.write",
action,
mount_id = %cfg.mount_id,
caller_id = %caller_id,
node_id = %node_id,
reason = "external_mount_op",
"👮🏻‍♂️ external mount mutation",
);
}
/// The id-string of a mount entry's parent: the parent's `ext:` id, or the
/// mount-root folder UUID when the entry is a direct child of the root.
pub fn mount_parent_id(cfg: &MountConfig, node_id: &str) -> String {
match node_id.rsplit_once('/') {
Some((parent, _)) => encode_child_id(cfg.mount_id, parent),
None => cfg.mount_id.to_string(),
}
}
/// Build a `FolderDto` for a mount directory from its stat. `parent_id` is the
/// id-string of the containing directory (mount-root UUID or an `ext:` id).
pub fn mount_folder_dto(cfg: &MountConfig, parent_id: &str, stat: &MountStat) -> FolderDto {
FolderDto {
etag: virtual_folder_etag(stat.modified_at),
id: encode_child_id(cfg.mount_id, stat.node_id.clone()),
name: node_name(stat.node_id.as_str()).to_owned(),
path: String::new(),
parent_id: Some(parent_id.to_owned()),
drive_id: cfg.drive_id,
created_at: stat.created_at,
modified_at: stat.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: Some(cfg.owner_id),
updated_by: Some(cfg.owner_id),
// Mount entries carry synthetic `ext:*` ids that don't exist in
// `auth.user_favorites` or `storage.role_grants`, so both flags
// are always false — they can't be favorited or grant-listed.
is_favorite: false,
is_shared: false,
}
}
/// 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()),
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: Some(cfg.owner_id),
updated_by: Some(cfg.owner_id),
is_favorite: false,
is_shared: false,
}
}
/// 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),
sort_date: None,
content_hash: String::new(),
etag: virtual_file_etag(entry.size, entry.modified_at),
created_by: Some(cfg.owner_id),
updated_by: Some(cfg.owner_id),
is_favorite: false,
is_shared: false,
}
}
/// 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 {
let name = node_name(stat.node_id.as_str());
let mime = stat.mime_type.as_str();
FileDto {
id: encode_child_id(cfg.mount_id, stat.node_id.clone()),
name: name.to_owned(),
path: String::new(),
size: stat.size,
mime_type: Arc::from(mime),
folder_id: Some(parent_id.to_owned()),
created_at: stat.created_at,
modified_at: stat.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(stat.size),
sort_date: None,
content_hash: String::new(),
etag: virtual_file_etag(stat.size, stat.modified_at),
created_by: Some(cfg.owner_id),
updated_by: Some(cfg.owner_id),
is_favorite: false,
is_shared: false,
}
}