1use std::{
2 fs,
3 path::{Path, PathBuf},
4};
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9#[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
17pub 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 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}