Before UI simplification - Clean Architecture complete

This commit is contained in:
wfx 2026-02-04 15:56:44 +01:00
parent fc73e4b76b
commit 6a4629bb47
13 changed files with 69 additions and 1848 deletions

View file

@ -5,8 +5,9 @@
use std::path::{Path, PathBuf};
use crate::domain::document::collection::DocumentCollection;
use crate::domain::document::core::content::DocumentContent;
use crate::domain::document::core::document::{DocResult, Renderable};
use crate::domain::document::core::document::DocResult;
use crate::domain::document::core::metadata::DocumentMeta;
use crate::infrastructure::filesystem::file_ops;
use crate::infrastructure::loaders::DocumentLoaderFactory;
@ -14,17 +15,12 @@ use crate::infrastructure::loaders::DocumentLoaderFactory;
/// Central document manager.
///
/// Orchestrates document loading, metadata extraction, and folder navigation.
/// Uses DocumentCollection (Domain Layer) for navigation logic.
pub struct DocumentManager {
/// Current document (if any).
current_document: Option<DocumentContent>,
/// Current document path.
current_path: Option<PathBuf>,
/// Document collection for navigation (Domain Layer abstraction).
collection: DocumentCollection,
/// Current document metadata.
current_metadata: Option<DocumentMeta>,
/// Folder entries for navigation.
folder_entries: Vec<PathBuf>,
/// Current index in folder entries.
current_index: Option<usize>,
/// Document loader factory.
loader: DocumentLoaderFactory,
}
@ -34,11 +30,8 @@ impl DocumentManager {
#[must_use]
pub fn new() -> Self {
Self {
current_document: None,
current_path: None,
collection: DocumentCollection::new(),
current_metadata: None,
folder_entries: Vec::new(),
current_index: None,
loader: DocumentLoaderFactory::new(),
}
}
@ -51,10 +44,11 @@ impl DocumentManager {
// Determine the actual file to open
let file_path = if path.is_dir() {
// Scan directory and find first supported file
self.scan_folder(path);
let paths = file_ops::collect_supported_files(path);
self.collection = DocumentCollection::from_paths(paths);
self.folder_entries
.first()
self.collection
.current_path()
.ok_or_else(|| anyhow::anyhow!("No supported files found in directory"))?
.clone()
} else {
@ -70,13 +64,15 @@ impl DocumentManager {
// Scan folder for navigation if not already done
if !path.is_dir() {
if let Some(parent) = file_path.parent() {
self.scan_folder(parent);
let paths = file_ops::collect_supported_files(parent);
self.collection = DocumentCollection::from_paths(paths);
// Find and set current document index
if let Some(idx) = self.collection.paths().iter().position(|p| p == &file_path) {
self.collection.goto(idx);
}
}
}
// Find current document index
self.current_index = self.folder_entries.iter().position(|p| p == &file_path);
// Generate thumbnails for multi-page documents (PDF)
let mut document = document;
if document.is_multi_page() {
@ -86,8 +82,8 @@ impl DocumentManager {
}
}
self.current_document = Some(document);
self.current_path = Some(file_path);
// Store document in collection
self.collection.set_current_document(document);
self.current_metadata = Some(metadata);
Ok(())
@ -96,26 +92,28 @@ impl DocumentManager {
/// Get the current document.
#[must_use]
pub fn current_document(&self) -> Option<&DocumentContent> {
self.current_document.as_ref()
self.collection.current_document()
}
/// Get a mutable reference to the current document.
#[must_use]
pub fn current_document_mut(&mut self) -> Option<&mut DocumentContent> {
self.current_document.as_mut()
self.collection.current_document_mut()
}
/// Get thumbnail handle for a specific page (read-only access).
/// Returns None if the thumbnail hasn't been generated yet.
#[must_use]
pub fn get_thumbnail_handle(&self, page: usize) -> Option<cosmic::widget::image::Handle> {
self.current_document.as_ref()?.get_thumbnail_handle(page)
self.collection
.current_document()?
.get_thumbnail_handle(page)
}
/// Get the current document path.
#[must_use]
pub fn current_path(&self) -> Option<&Path> {
self.current_path.as_deref()
self.collection.current_path().map(|p| p.as_path())
}
/// Get the current document metadata.
@ -124,38 +122,33 @@ impl DocumentManager {
self.current_metadata.as_ref()
}
/// Get folder entries for navigation.
/// Get all folder entries for navigation.
#[must_use]
pub fn folder_entries(&self) -> &[PathBuf] {
&self.folder_entries
self.collection.paths()
}
/// Get current index in folder.
#[must_use]
pub fn current_index(&self) -> Option<usize> {
self.current_index
self.collection.current_index()
}
/// Navigate to the next document in the folder.
///
/// Wraps around to the first document when at the end.
pub fn next_document(&mut self) -> Option<PathBuf> {
if self.folder_entries.is_empty() {
// Use DocumentCollection navigation
if self.collection.has_next() {
self.collection.next();
} else if !self.collection.is_empty() {
// Wrap around to first
self.collection.goto(0);
} else {
return None;
}
let new_index = match self.current_index {
Some(idx) => {
if idx + 1 < self.folder_entries.len() {
idx + 1
} else {
0 // Wrap around to first
}
}
None => 0,
};
let next_path = self.folder_entries.get(new_index)?.clone();
let next_path = self.collection.current_path()?.clone();
if self.open_document(&next_path).is_ok() {
Some(next_path)
} else {
@ -167,22 +160,18 @@ impl DocumentManager {
///
/// Wraps around to the last document when at the beginning.
pub fn previous_document(&mut self) -> Option<PathBuf> {
if self.folder_entries.is_empty() {
// Use DocumentCollection navigation
if self.collection.has_previous() {
self.collection.previous();
} else if !self.collection.is_empty() {
// Wrap around to last
let last_idx = self.collection.len() - 1;
self.collection.goto(last_idx);
} else {
return None;
}
let new_index = match self.current_index {
Some(idx) => {
if idx > 0 {
idx - 1
} else {
self.folder_entries.len() - 1 // Wrap around to last
}
}
None => self.folder_entries.len().saturating_sub(1),
};
let prev_path = self.folder_entries.get(new_index)?.clone();
let prev_path = self.collection.current_path()?.clone();
if self.open_document(&prev_path).is_ok() {
Some(prev_path)
} else {
@ -193,77 +182,30 @@ impl DocumentManager {
/// Close the current document.
#[allow(dead_code)]
pub fn close_document(&mut self) {
self.current_document = None;
self.current_path = None;
self.collection.clear_current_document();
self.current_metadata = None;
}
/// Scan a folder for supported documents.
fn scan_folder(&mut self, folder: &Path) {
self.folder_entries = file_ops::collect_supported_files(folder);
}
/// Extract metadata from a document.
fn extract_metadata(&self, path: &Path, document: &DocumentContent) -> DocumentMeta {
use crate::domain::document::core::metadata::{BasicMeta, DocumentMeta, ExifMeta};
let info = document.info();
let (width, height) = document.dimensions();
let file_name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_string();
let file_path = path.to_string_lossy().to_string();
let file_size = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0);
let format = info.format;
let color_type = format!("{}", document.kind());
let basic = BasicMeta {
file_name,
file_path,
format,
width,
height,
file_size,
color_type,
};
// Extract EXIF data for raster images (JPEG, TIFF)
let exif =
if document.kind() == crate::domain::document::core::content::DocumentKind::Raster {
file_ops::read_file_bytes(path).and_then(|bytes| ExifMeta::from_bytes(&bytes))
} else {
None
};
DocumentMeta { basic, exif }
// Use the document's own extract_meta() method
// This properly delegates to the type-specific implementation
// (RasterDocument, VectorDocument, or PortableDocument)
document.extract_meta(path)
}
/// Check if there is a next document available.
#[must_use]
#[allow(dead_code)]
pub fn has_next(&self) -> bool {
if let Some(current) = self.current_index {
current + 1 < self.folder_entries.len()
} else {
false
}
self.collection.has_next()
}
/// Check if there is a previous document available.
#[must_use]
#[allow(dead_code)]
pub fn has_previous(&self) -> bool {
if let Some(current) = self.current_index {
current > 0
} else {
false
}
self.collection.has_previous()
}
}