ferrite_navigation/
manager.rs

1use 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        // Collect valid image paths
31        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            // Get next images circularly
78            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            // Get previous images circularly
84            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    /// Remove a deleted file from the directory listing and navigate to the next available image
119    /// Returns the path of the next image to display, or None if no images remain
120    #[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            // Adjust current index if necessary
131            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}