diff --git a/src/application/dtos/file_dto.rs b/src/application/dtos/file_dto.rs index 2f05e6fb..18cc815a 100644 --- a/src/application/dtos/file_dto.rs +++ b/src/application/dtos/file_dto.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::domain::entities::file::File; use serde::{Deserialize, Serialize}; @@ -20,8 +22,9 @@ pub struct FileDto { /// Size in bytes pub size: u64, - /// MIME type - pub mime_type: String, + /// MIME type — `Arc` because MIME values repeat across files + /// and DTOs are cloned on every request (clone is O(1) atomic increment). + pub mime_type: Arc, /// Parent folder ID pub folder_id: Option, @@ -32,15 +35,15 @@ pub struct FileDto { /// Last modification timestamp pub modified_at: u64, - // ── Pre-computed display fields ── + // ── Pre-computed display fields (Arc: values come from static tables) ── /// FontAwesome icon CSS class (e.g. "fas fa-file-image") - pub icon_class: String, + pub icon_class: Arc, /// Extra CSS class for icon styling (e.g. "image-icon", "" when default) - pub icon_special_class: String, + pub icon_special_class: Arc, /// Human-readable file category (e.g. "Image", "Document") - pub category: String, + pub category: Arc, /// Human-readable formatted size (e.g. "3.27 MB") pub size_formatted: String, @@ -61,13 +64,13 @@ impl From for FileDto { name: name.to_string(), path: file.path_string().to_string(), size, - mime_type: mime.to_string(), + mime_type: Arc::from(mime), folder_id: file.folder_id().map(String::from), created_at: file.created_at(), modified_at: file.modified_at(), - icon_class: icon_class_for(name, mime).to_string(), - icon_special_class: icon_special_class_for(name, mime).to_string(), - category: category_for(name, mime).to_string(), + 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(size), owner_id: file.owner_id().map(String::from), } @@ -84,7 +87,7 @@ impl From for File { dto.name, dto.path, dto.size, - dto.mime_type, + dto.mime_type.to_string(), dto.folder_id, dto.created_at, dto.modified_at, @@ -100,13 +103,13 @@ impl FileDto { name: "stub-file".to_string(), path: "/stub/path".to_string(), size: 0, - mime_type: "application/octet-stream".to_string(), + mime_type: Arc::from("application/octet-stream"), folder_id: None, created_at: 0, modified_at: 0, - icon_class: "fas fa-file".to_string(), - icon_special_class: String::new(), - category: "Document".to_string(), + icon_class: Arc::from("fas fa-file"), + icon_special_class: Arc::from(""), + category: Arc::from("Document"), size_formatted: "0 Bytes".to_string(), owner_id: None, } diff --git a/src/application/dtos/folder_dto.rs b/src/application/dtos/folder_dto.rs index 2c6466d8..32e7ce4e 100644 --- a/src/application/dtos/folder_dto.rs +++ b/src/application/dtos/folder_dto.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::domain::entities::folder::Folder; use serde::{Deserialize, Serialize}; @@ -53,15 +55,15 @@ pub struct FolderDto { /// Whether this is a root folder pub is_root: bool, - // ── Pre-computed display fields ── + // ── Pre-computed display fields (Arc: always identical values) ── /// FontAwesome icon CSS class (always "fas fa-folder") - pub icon_class: String, + pub icon_class: Arc, /// Extra CSS class for icon styling (always "folder-icon") - pub icon_special_class: String, + pub icon_special_class: Arc, /// Human-readable category (always "Folder") - pub category: String, + pub category: Arc, } impl From for FolderDto { @@ -77,9 +79,9 @@ impl From for FolderDto { created_at: folder.created_at(), modified_at: folder.modified_at(), is_root, - icon_class: "fas fa-folder".to_string(), - icon_special_class: "folder-icon".to_string(), - category: "Folder".to_string(), + icon_class: Arc::from("fas fa-folder"), + icon_special_class: Arc::from("folder-icon"), + category: Arc::from("Folder"), } } } @@ -112,9 +114,9 @@ impl FolderDto { created_at: 0, modified_at: 0, is_root: true, - icon_class: "fas fa-folder".to_string(), - icon_special_class: "folder-icon".to_string(), - category: "Folder".to_string(), + icon_class: Arc::from("fas fa-folder"), + icon_special_class: Arc::from("folder-icon"), + category: Arc::from("Folder"), } } } diff --git a/src/application/ports/file_ports.rs b/src/application/ports/file_ports.rs index ba791c55..bbb497bf 100644 --- a/src/application/ports/file_ports.rs +++ b/src/application/ports/file_ports.rs @@ -93,7 +93,7 @@ pub enum OptimizedFileContent { /// Small-file content (possibly transcoded / compressed) already in RAM. Bytes { data: Bytes, - mime_type: String, + mime_type: Arc, was_transcoded: bool, }, /// Memory-mapped file (10–100 MB). diff --git a/src/application/services/file_retrieval_service.rs b/src/application/services/file_retrieval_service.rs index 83666078..fdf8c05f 100644 --- a/src/application/services/file_retrieval_service.rs +++ b/src/application/services/file_retrieval_service.rs @@ -61,7 +61,7 @@ impl FileRetrievalService { mime: &str, file_size: u64, accept_webp: bool, - ) -> Option<(Bytes, String)> { + ) -> Option<(Bytes, Arc)> { if !accept_webp { return None; } @@ -81,7 +81,7 @@ impl FileRetrievalService { transcoded.len(), (1.0 - transcoded.len() as f64 / content.len().max(1) as f64) * 100.0 ); - Some((transcoded, webp_mime)) + Some((transcoded, Arc::from(&*webp_mime))) } _ => None, } @@ -152,7 +152,7 @@ impl FileRetrievalService { // Store in cache if let Some(cache) = &self.content_cache { let etag: Arc = format!("\"{}-{}\"", id, modified_at).into(); - let ct: Arc = Arc::from(&*mime_type); + let ct: Arc = mime_type.clone(); cache .put(id.to_string(), content_bytes.clone(), etag, ct) .await; diff --git a/src/application/services/search_service.rs b/src/application/services/search_service.rs index c0e9a9d7..efc987e6 100644 --- a/src/application/services/search_service.rs +++ b/src/application/services/search_service.rs @@ -158,7 +158,7 @@ impl SearchService { name: file.name.clone(), path: file.path.clone(), size: file.size, - mime_type: file.mime_type.clone(), + mime_type: file.mime_type.to_string(), folder_id: file.folder_id.clone(), created_at: file.created_at, modified_at: file.modified_at, diff --git a/src/common/stubs.rs b/src/common/stubs.rs index 5b0b8485..2b849b6c 100644 --- a/src/common/stubs.rs +++ b/src/common/stubs.rs @@ -505,7 +505,7 @@ impl FileRetrievalUseCase for StubFileRetrievalUseCase { FileDto::default(), OptimizedFileContent::Bytes { data: Bytes::new(), - mime_type: String::new(), + mime_type: Arc::from(""), was_transcoded: false, }, )) diff --git a/src/infrastructure/services/path_resolver_service.rs b/src/infrastructure/services/path_resolver_service.rs index d7f81896..ed54e2b2 100644 --- a/src/infrastructure/services/path_resolver_service.rs +++ b/src/infrastructure/services/path_resolver_service.rs @@ -149,9 +149,9 @@ impl PathResolverService { created_at: created_at as u64, modified_at: modified_at as u64, is_root: false, - icon_class: "fas fa-folder".to_string(), - icon_special_class: "folder-icon".to_string(), - category: "Folder".to_string(), + icon_class: Arc::from("fas fa-folder"), + icon_special_class: Arc::from("folder-icon"), + category: Arc::from("Folder"), })), _ => { let mime = mime_type.unwrap_or_else(|| "application/octet-stream".to_string()); @@ -161,13 +161,13 @@ impl PathResolverService { name: name.clone(), path: res_path, size: sz, - mime_type: mime.clone(), + mime_type: Arc::from(&*mime), folder_id, created_at: created_at as u64, modified_at: modified_at as u64, - icon_class: icon_class_for(&name, &mime).to_string(), - icon_special_class: icon_special_class_for(&name, &mime).to_string(), - category: category_for(&name, &mime).to_string(), + 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(sz), owner_id: user_id, })) diff --git a/src/interfaces/api/handlers/file_handler.rs b/src/interfaces/api/handlers/file_handler.rs index 26c95a28..9152c352 100644 --- a/src/interfaces/api/handlers/file_handler.rs +++ b/src/interfaces/api/handlers/file_handler.rs @@ -433,7 +433,7 @@ impl FileHandler { Ok(stream) => { return Response::builder() .status(StatusCode::PARTIAL_CONTENT) - .header(header::CONTENT_TYPE, &file_dto.mime_type) + .header(header::CONTENT_TYPE, &*file_dto.mime_type) .header(header::CONTENT_DISPOSITION, &disposition) .header(header::CONTENT_LENGTH, range_length) .header( @@ -488,7 +488,7 @@ impl FileHandler { .into_response(), OptimizedFileContent::Mmap(mmap_data) => Response::builder() .status(StatusCode::OK) - .header(header::CONTENT_TYPE, &file_dto.mime_type) + .header(header::CONTENT_TYPE, &*file_dto.mime_type) .header(header::CONTENT_DISPOSITION, &disposition) .header(header::CONTENT_LENGTH, mmap_data.len()) .header(header::ETAG, &etag) @@ -502,7 +502,7 @@ impl FileHandler { .into_response(), OptimizedFileContent::Stream(pinned_stream) => Response::builder() .status(StatusCode::OK) - .header(header::CONTENT_TYPE, &file_dto.mime_type) + .header(header::CONTENT_TYPE, &*file_dto.mime_type) .header(header::CONTENT_DISPOSITION, &disposition) .header(header::CONTENT_LENGTH, file_dto.size) .header(header::ETAG, &etag) diff --git a/src/interfaces/api/handlers/webdav_handler.rs b/src/interfaces/api/handlers/webdav_handler.rs index 0c13212e..6e8ad4b2 100644 --- a/src/interfaces/api/handlers/webdav_handler.rs +++ b/src/interfaces/api/handlers/webdav_handler.rs @@ -286,9 +286,9 @@ async fn handle_propfind( created_at: Utc::now().timestamp() as u64, modified_at: Utc::now().timestamp() as u64, is_root: true, - icon_class: "fas fa-folder".to_string(), - icon_special_class: "folder-icon".to_string(), - category: "Folder".to_string(), + icon_class: Arc::from("fas fa-folder"), + icon_special_class: Arc::from("folder-icon"), + category: Arc::from("Folder"), }; return build_streaming_propfind_response( @@ -609,7 +609,7 @@ async fn handle_get( // Build streaming response using Content-Length from metadata Ok(Response::builder() .status(StatusCode::OK) - .header(header::CONTENT_TYPE, file.mime_type) + .header(header::CONTENT_TYPE, &*file.mime_type) .header(header::CONTENT_LENGTH, file.size) .header(header::ETAG, format!("\"{}\"", file.id)) .header( @@ -658,7 +658,7 @@ async fn handle_head( Ok(ResolvedResource::File(file)) => { return Ok(Response::builder() .status(StatusCode::OK) - .header(header::CONTENT_TYPE, &file.mime_type) + .header(header::CONTENT_TYPE, &*file.mime_type) .header(header::CONTENT_LENGTH, file.size) .header(header::ETAG, format!("\"{}\"", file.id)) .header( @@ -693,7 +693,7 @@ async fn handle_head( Ok(Response::builder() .status(StatusCode::OK) - .header(header::CONTENT_TYPE, &file.mime_type) + .header(header::CONTENT_TYPE, &*file.mime_type) .header(header::CONTENT_LENGTH, file.size) .header(header::ETAG, format!("\"{}\"", file.id)) .header(