// SPDX-License-Identifier: GPL-3.0-or-later // src/document/manager.rs // // Document Manager – single entry point via SessionCommand. use crate::document::command::SessionCommand; use crate::document::loader::{load_document, scan_directory_into}; use crate::document::session::{CollectionKind, SessionData}; use crate::document::store; use crate::document::DocumentContent; use crate::error::Error; use log::warn; use std::path::Path; #[allow(dead_code)] #[derive(Debug)] pub enum DocumentState { Empty, Loading, Loaded(DocumentContent), Error(Error), } pub struct DocumentManager { sessions: Vec, pub active_session_name: Option, pub state: DocumentState, } impl Default for DocumentManager { fn default() -> Self { Self { sessions: Vec::new(), active_session_name: None, state: DocumentState::Empty, } } } impl DocumentManager { pub fn new() -> Self { Self::default() } /// The single public entry point for all session/collection state changes. pub fn handle(&mut self, command: SessionCommand) { match command { // -- Lifecycle -- SessionCommand::New { name, kind } => { self.create_session(&name, kind); } SessionCommand::Open { path } => { self.open_session_file(&path); } SessionCommand::Select { name } => { if self.session(&name).is_some() { self.active_session_name = Some(name); } } // -- Collection management -- SessionCommand::AddDirectory { dir } => { if let Some(session) = self.active_session_mut() { scan_directory_into(session, &dir); } } SessionCommand::AddDocument { path } => { self.add_document(&path); } SessionCommand::CopyDocument { source, target, source_index, target_index, } => { self.copy_document(&source, &target, source_index, target_index); } SessionCommand::RemoveDocument { index } => { self.remove_document(index); } // -- Navigation -- SessionCommand::NextDocument => self.navigate_next(), SessionCommand::PreviousDocument => self.navigate_previous(), SessionCommand::SelectDocument { index } => self.select_document(index), } } /// Read-only access to a session by name. pub fn session(&self, name: &str) -> Option<&SessionData> { self.sessions.iter().find(|s| s.name == name) } /// Read-only access to the active session. pub fn active_session(&self) -> Option<&SessionData> { let name = self.active_session_name.as_deref()?; self.sessions.iter().find(|s| s.name == name) } // -- Private helpers -- fn session_mut(&mut self, name: &str) -> Option<&mut SessionData> { self.sessions.iter_mut().find(|s| s.name == name) } fn active_session_mut(&mut self) -> Option<&mut SessionData> { let name = self.active_session_name.clone()?; self.sessions.iter_mut().find(|s| s.name == name) } fn create_session(&mut self, name: &str, kind: CollectionKind) { if self.session(name).is_none() { self.sessions .push(SessionData::new(name, kind, None, Vec::new())); } } fn open_session_file(&mut self, path: &Path) { match store::load_sessions(path) { Ok(mut loaded) => { for session in loaded.iter_mut() { match session.kind { CollectionKind::DirectoryBrowser => { if let Some(dir) = session.path.clone() { scan_directory_into(session, &dir); } } CollectionKind::DocumentCollection => { session.items.retain(|item| { if item.path.exists() { true } else { warn!( "Session '{}': {}", session.name, Error::NotFound(item.path.display().to_string()) ); false } }); session.current_index = match session.current_index { Some(i) if i < session.items.len() => Some(i), _ if !session.items.is_empty() => Some(0), _ => None, }; } } } let browser_name = loaded .iter() .find(|s| s.kind == CollectionKind::DirectoryBrowser) .map(|s| s.name.clone()); self.sessions = loaded; self.active_session_name = browser_name; self.state = DocumentState::Empty; } Err(e) => { self.state = DocumentState::Error(e); } } } fn add_document(&mut self, path: &Path) { self.state = DocumentState::Loading; match load_document(path) { Ok(content) => { if let Some(session) = self.active_session_mut() { if let Some(idx) = session.items.iter().position(|item| item.path == path) { session.current_index = Some(idx); } } self.state = DocumentState::Loaded(content); } Err(e) => { self.state = DocumentState::Error(e); } } } fn select_document(&mut self, index: usize) { if let Some(session) = self.active_session_mut() { if index < session.items.len() { session.current_index = Some(index); } } } fn navigate_next(&mut self) { if let Some(session) = self.active_session_mut() { if let Some(idx) = session.current_index { if idx + 1 < session.items.len() { session.current_index = Some(idx + 1); } } } } fn navigate_previous(&mut self) { if let Some(session) = self.active_session_mut() { if let Some(idx) = session.current_index { if idx > 0 { session.current_index = Some(idx - 1); } } } } fn copy_document( &mut self, source_name: &str, target_name: &str, source_index: usize, target_index: usize, ) { let item = match self.session(source_name) { Some(s) => match s.items.get(source_index) { Some(item) => item.clone(), None => return, }, None => { self.state = DocumentState::Error(Error::Session(format!( "Source session '{}' not found", source_name ))); return; } }; let target = match self.session(target_name) { Some(s) => s, None => { self.state = DocumentState::Error(Error::Session(format!( "Target session '{}' not found", target_name ))); return; } }; if target.kind != CollectionKind::DocumentCollection { self.state = DocumentState::Error(Error::Session( "Target must be a DocumentCollection".to_string(), )); return; } if target .items .iter() .any(|i| i.path == item.path && i.page_index == item.page_index) { self.state = DocumentState::Error(Error::Session("Item already exists".to_string())); return; } if let Some(target) = self.session_mut(target_name) { let idx = target_index.min(target.items.len()); target.items.insert(idx, item); } } fn remove_document(&mut self, index: usize) { let session = match self.active_session_mut() { Some(s) => s, None => return, }; if session.kind != CollectionKind::DocumentCollection { self.state = DocumentState::Error(Error::Session( "Can only remove from a DocumentCollection".to_string(), )); return; } if index >= session.items.len() { return; } session.items.remove(index); let new_len = session.items.len(); if new_len == 0 { session.current_index = None; } else if let Some(current) = session.current_index { if current >= new_len { session.current_index = Some(new_len - 1); } } } }