pispas_modules/
pdf_manager.rs

1use std::fs;
2use std::path::{PathBuf};
3use tokio::task::spawn_blocking;
4use easy_trace::prelude::{debug, error, info};
5
6/// Maximum number of PDF files to retain in the jobs directory.
7const MAX_PDFS: usize = 31;
8
9
10/// Manages PDF files in the jobs directory, ensuring the number of files is within the defined limit.
11#[derive(Debug)]
12pub struct PDFManager {
13    /// List of managed PDF file paths.
14    pdf_files: Vec<PathBuf>,
15}
16
17impl PDFManager {
18    /// Creates a new instance of `PDFManager` and initializes the list of PDF files.
19    pub fn new() -> Self {
20        let pdf_files = Self::initialize_pdf_list().unwrap_or_else(|e| {
21            error!("Failed to initialize PDF files: {}", e);
22            Vec::new()
23        });
24        info!("Initialized with {} PDF files.", pdf_files.len());
25        let mut manager = PDFManager { pdf_files };
26        manager.maintain_pdf_files(); // Limpia los PDFs excedentes al arrancar
27        manager
28    }
29
30    /// Initializes the list of PDF files from the jobs directory.
31    ///
32    /// This method reads the jobs directory, collects PDF files,
33    /// and sorts them by their modification time.
34    fn initialize_pdf_list() -> sharing::PisPasResult<Vec<PathBuf>> {
35        let mut pdf_files: Vec<_> = fs::read_dir(sharing::paths::jobs_dir())
36            .expect("Failed to read jobs directory")
37            .filter_map(|entry| {
38                let entry = entry.ok()?; // Safely handle errors for each entry
39                let metadata = entry.metadata().ok()?; // Get metadata for each entry
40                let modified = metadata.modified().ok()?; // Get last modified time
41                Some((entry.path(), modified)) // Collect the path and modification time
42            })
43            .filter(|(path, _)| {
44                path.extension().map(|ext| ext == "pdf").unwrap_or(false) // Only include PDF files
45            })
46            .collect();
47
48        // Sort the files by modification date (oldest first)
49        pdf_files.sort_by_key(|&(_, modified)| modified);
50        Ok(pdf_files.into_iter().map(|(path, _)| path).collect())
51    }
52
53    /// Adds a new PDF file to the manager and maintains the directory's size within the limit.
54    ///
55    /// If the file already exists in the list, it won't be added again.
56    pub fn add_pdf_file(&mut self, pdf_file: PathBuf) {
57        if !self.pdf_files.contains(&pdf_file) {
58            self.pdf_files.push(pdf_file);
59        }
60        self.maintain_pdf_files();
61    }
62
63    /// Ensures the number of PDF files is within the defined limit by removing the oldest files.    
64    fn maintain_pdf_files(&mut self) {
65        while self.pdf_files.len() > MAX_PDFS {            
66            let oldest_pdf = self.pdf_files[0].clone();
67            match fs::remove_file(&oldest_pdf) {
68                Ok(_) => {
69                    debug!("Deleted old PDF file: {:?}", oldest_pdf);
70                    self.pdf_files.remove(0); 
71                }
72                Err(ref e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
73                    error!(
74                    "File {:?} is in use, moving to the end of the list: {}",
75                    oldest_pdf, e
76                );                    
77                    let pdf = self.pdf_files.remove(0);
78                    self.pdf_files.push(pdf);
79                }
80                Err(e) => {
81                    error!("Failed to delete file {:?}: {}", oldest_pdf, e);                    
82                    self.pdf_files.remove(0);
83                }
84            }
85        }
86        debug!("Remaining PDF files: {}", self.pdf_files.len());
87    }
88  
89    /// Asynchronous method to manage the job directory and keep the file count within the limit.
90    ///
91    /// This method performs the cleanup operation in a blocking task.
92    pub async fn _maintain_jobs_directory(&self) {
93        spawn_blocking(move || {
94            let mut pdf_files = Self::initialize_pdf_list().unwrap_or_else(|e| {
95                error!("Failed to initialize PDF files: {}", e);
96                Vec::new()
97            });
98
99            while pdf_files.len() > MAX_PDFS {
100                // Get the oldest file without causing a reference conflict
101                if let Some(oldest_pdf) = pdf_files.get(0) {
102                    // Remove the file after obtaining the immutable reference
103                    if let Err(e) = fs::remove_file(oldest_pdf) {
104                        error!("Error deleting file {:?}: {}", oldest_pdf, e);
105                    } else {
106                        // Remove the file from the list
107                        pdf_files.remove(0); // Delete the oldest file
108                    }
109                }
110            }
111
112            info!("Remaining PDF files: {}", pdf_files.len());
113        })
114            .await
115            .unwrap();
116    }
117}