noctua/core/src/document/manager.rs
mow e928c61107 refactor: rename commands to SessionCommand/DocumentCommand split
- Rename SessionKind → CollectionKind
- Replace monolithic DocumentCommand with SessionCommand (lifecycle + collection)
  and DocumentCommand (reserved for future document operations)
- Separate session creation from population (New → Select → AddDirectory)
- Collection commands operate on active session, no hidden side effects
- RemoveDocument/CopyDocument use explicit indices (no more Option)
2026-03-08 09:01:20 +01:00

291 lines
9.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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<SessionData>,
pub active_session_name: Option<String>,
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);
}
}
}
}