ferrite_navigation/
manager.rs1use crate::error::{NavigationError, Result};
2use ferrite_image::SupportedFormats;
3use std::{
4 fs,
5 path::{Path, PathBuf},
6};
7use tracing::{info, instrument};
8
9pub struct NavigationManager {
10 directory_images: Vec<PathBuf>,
11 current_index: usize,
12}
13
14impl NavigationManager {
15 pub fn new() -> Self {
16 Self { directory_images: Vec::new(), current_index: 0 }
17 }
18
19 #[instrument(skip(self, image_path), fields(path = ?image_path))]
20 pub fn load_current_directory(&mut self, image_path: &Path) -> Result<()> {
21 let absolute_path = fs::canonicalize(image_path)
22 .map_err(NavigationError::DirectoryAccess)?;
23
24 let parent_dir = absolute_path.parent().ok_or_else(|| {
25 NavigationError::InvalidPath(image_path.to_path_buf())
26 })?;
27
28 info!("Loading images from directory: {}", parent_dir.display());
29
30 self.directory_images = fs::read_dir(parent_dir)
32 .map_err(NavigationError::DirectoryAccess)?
33 .filter_map(|entry| {
34 entry.ok().and_then(|e| {
35 let path = e.path();
36 if path.is_file()
37 && SupportedFormats::is_supported(path.extension())
38 {
39 Some(path)
40 } else {
41 None
42 }
43 })
44 })
45 .collect();
46
47 self.directory_images.sort();
48
49 self.current_index = self
50 .directory_images
51 .iter()
52 .position(|p| p == &absolute_path)
53 .unwrap_or(0);
54
55 info!(
56 "Found {} images in directory, current image at index {}",
57 self.directory_images.len(),
58 self.current_index
59 );
60
61 Ok(())
62 }
63
64 pub fn get_nearby_paths(
65 &self,
66 count: usize,
67 ) -> (Vec<PathBuf>, Vec<PathBuf>) {
68 if self.directory_images.is_empty() {
69 return (Vec::new(), Vec::new());
70 }
71
72 let total_images = self.directory_images.len();
73 let mut next_paths = Vec::with_capacity(count);
74 let mut prev_paths = Vec::with_capacity(count);
75
76 for i in 1..=count {
77 let next_index = (self.current_index + i) % total_images;
79 if next_index != self.current_index {
80 next_paths.push(self.directory_images[next_index].clone());
81 }
82
83 let prev_index = if i <= self.current_index {
85 self.current_index - i
86 } else {
87 total_images - (i - self.current_index)
88 };
89 if prev_index < total_images && prev_index != self.current_index {
90 prev_paths.push(self.directory_images[prev_index].clone());
91 }
92 }
93
94 (prev_paths, next_paths)
95 }
96
97 pub fn next_image(&mut self) -> Option<PathBuf> {
98 if self.directory_images.is_empty() {
99 return None;
100 }
101 self.current_index =
102 (self.current_index + 1) % self.directory_images.len();
103 Some(self.directory_images[self.current_index].clone())
104 }
105
106 pub fn previous_image(&mut self) -> Option<PathBuf> {
107 if self.directory_images.is_empty() {
108 return None;
109 }
110 self.current_index = if self.current_index == 0 {
111 self.directory_images.len() - 1
112 } else {
113 self.current_index - 1
114 };
115 Some(self.directory_images[self.current_index].clone())
116 }
117
118 #[instrument(skip(self, deleted_path), fields(deleted_path = ?deleted_path))]
121 pub fn remove_deleted_file(&mut self, deleted_path: &std::path::Path) -> Option<PathBuf> {
122 if let Some(pos) = self.directory_images.iter().position(|p| p == deleted_path) {
123 self.directory_images.remove(pos);
124
125 if self.directory_images.is_empty() {
126 info!("No more images in directory after deletion");
127 return None;
128 }
129
130 if self.current_index >= self.directory_images.len() {
132 self.current_index = self.directory_images.len() - 1;
133 } else if pos <= self.current_index && self.current_index > 0 {
134 self.current_index -= 1;
135 }
136
137 let next_path = self.directory_images[self.current_index].clone();
138 info!("Next image after deletion: {}", next_path.display());
139 Some(next_path)
140 } else {
141 info!("Deleted file was not in current directory listing");
142 None
143 }
144 }
145}