safe_remove/
storage.rs

1use std::{
2    fs,
3    path::{Path, PathBuf},
4};
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9/// Structure to represent a safely removed file
10#[derive(Debug, Serialize, Deserialize, Clone)]
11pub struct SafeFile {
12    pub original_path: PathBuf,
13    pub moved_path: PathBuf,
14    pub deleted_at: DateTime<Utc>,
15}
16
17/// StorageManager handles all operations related to safe storage
18pub struct StorageManager {
19    pub safe_dir: PathBuf,
20    pub metadata_file: PathBuf,
21    pub safe_files: Vec<SafeFile>,
22}
23
24impl StorageManager {
25    pub fn new() -> Result<Self, String> {
26        let proj_dirs = directories::ProjectDirs::from("com", "larpi", "srm")
27            .ok_or_else(|| "Cannot determine project directories".to_string())?;
28        let data_dir = proj_dirs.data_dir();
29        fs::create_dir_all(data_dir)
30            .map_err(|e| format!("Failed to create safe directory: {}", e))?;
31
32        let metadata_file = data_dir.join("metadata.yaml");
33        let safe_files: Vec<SafeFile> = if metadata_file.exists() {
34            let contents =
35                fs::read_to_string(&metadata_file).expect("Failed to read metadata file");
36            serde_yaml::from_str(&contents).expect("Failed to parse metadata file")
37        } else {
38            Vec::new()
39        };
40
41        Ok(StorageManager {
42            safe_dir: data_dir.to_path_buf(),
43            metadata_file,
44            safe_files,
45        })
46    }
47
48    pub fn save_metadata(&self) -> Result<(), String> {
49        let serialized = serde_yaml::to_string(&self.safe_files)
50            .map_err(|e| format!("Failed to serialize metadata: {}", e))?;
51        fs::write(&self.metadata_file, serialized)
52            .map_err(|e| format!("Failed to write metadata: {}", e))
53    }
54
55    pub fn add_file(&mut self, safe_file: SafeFile) {
56        self.safe_files.push(safe_file);
57    }
58
59    /// Remove a file from the safe storage metadata
60    pub fn remove_file(&mut self, moved_path: &Path) {
61        self.safe_files.retain(|f| f.moved_path != moved_path);
62    }
63
64    pub fn find_safe_file(&self, file_name: &str) -> Option<&SafeFile> {
65        self.safe_files.iter().find(|f| {
66            f.moved_path
67                .file_name()
68                .map(|n| n.to_string_lossy() == file_name)
69                .unwrap_or(false)
70        })
71    }
72
73    pub fn cleanup(&mut self) -> Result<(), String> {
74        let now = Utc::now();
75        let mut updated = Vec::new();
76
77        for file in self.safe_files.iter() {
78            if now >= file.deleted_at {
79                if let Err(e) = fs::remove_file(&file.moved_path) {
80                    eprintln!("Failed to deleted {:?}: {}", file.moved_path, e);
81                    updated.push(file.clone());
82                } else {
83                    println!(
84                        "Deleted '{}' from safe storage",
85                        file.moved_path
86                            .file_name()
87                            .unwrap_or_default()
88                            .to_string_lossy()
89                    );
90                }
91            } else {
92                updated.push(file.clone());
93            }
94        }
95
96        self.safe_files = updated;
97        self.save_metadata()
98    }
99
100    pub fn get_safe_files(&self) -> Vec<&SafeFile> {
101        self.safe_files.iter().collect()
102    }
103
104    pub fn list_files(&self) {
105        if self.safe_files.is_empty() {
106            println!("No files stored in the safe storage");
107            return;
108        }
109
110        println!(
111            "{:<30} {:<50} {:<25}",
112            "File Name", "Original Path", "Deleted At"
113        );
114        println!("{}", "-".repeat(110));
115
116        for file in &self.safe_files {
117            let file_name = file
118                .moved_path
119                .file_name()
120                .unwrap_or_default()
121                .to_string_lossy();
122            let original_path = file.original_path.to_string_lossy();
123            let deleted_at = file.deleted_at.with_timezone(&chrono::Local).to_rfc3339();
124
125            println!("{:<30} {:<50} {:<25}", file_name, original_path, deleted_at);
126        }
127    }
128
129    pub fn cleanup_all_files(&mut self) -> Result<(), String> {
130        self.safe_files.iter().for_each(|f| {
131            if let Err(e) = fs::remove_file(&f.moved_path) {
132                eprintln!("Failed to delete {:?}: {}", f.moved_path, e);
133            } else {
134                println!(
135                    "Deleted '{}' from safe storage",
136                    f.moved_path
137                        .file_name()
138                        .unwrap_or_default()
139                        .to_string_lossy()
140                );
141            }
142        });
143
144        self.safe_files.clear();
145        self.save_metadata()
146    }
147}