1use std::{
2 fs,
3 path::PathBuf,
4 process::{Command, Stdio},
5 time::Duration,
6};
7
8use chrono::Utc;
9use colored::Colorize;
10
11use crate::{
12 config::Config,
13 storage::{SafeFile, StorageManager},
14 utils::parse_duration,
15};
16
17pub async fn remove_command(
18 config: &Config,
19 duration_str: Option<String>,
20 files: Vec<String>,
21) -> Result<(), String> {
22 let mut storage = StorageManager::new()?;
23
24 let duration_str = duration_str
25 .as_deref()
26 .or(config.default_duration.as_deref());
27 let duration = match duration_str {
28 Some(s) => match parse_duration(s) {
29 Ok(d) => d,
30 Err(e) => return Err(format!("Error parsing duration '{}': {}", s, e)),
31 },
32 None => Duration::from_secs(7 * 24 * 60 * 60), };
34
35 storage.cleanup()?;
36
37 for file in files {
38 let path = PathBuf::from(&file)
39 .canonicalize()
40 .map_err(|e| e.to_string())?;
41 if !path.exists() {
42 eprintln!("{}: File does not exist: {}", "Error".red(), file);
43 continue;
44 }
45
46 let file_name = match path.file_name() {
47 Some(name) => name,
48 None => {
49 eprintln!("{}: Invalid file name: {}", "Error".red(), file);
50 continue;
51 }
52 };
53 let mut moved_path = storage.safe_dir.join(file_name);
54
55 if moved_path.exists() {
57 let timestamp = Utc::now().format("%Y%m%d%H%M%S");
58 let stem = path.file_stem().unwrap().to_string_lossy();
59 let ext = path
60 .extension()
61 .map_or_else(|| "".to_string(), |e| format!(".{}", e.to_string_lossy()));
62 let new_name = if ext.is_empty() {
63 format!("{}_{}", stem, timestamp)
64 } else {
65 format!("{}_{}.{}", stem, timestamp, ext)
66 };
67 moved_path = storage.safe_dir.join(new_name);
68 }
69
70 if let Err(e) = fs::rename(&path, &moved_path) {
71 eprintln!(
72 "{}: Failed to move '{}' to safe storage: {}",
73 "Error".red(),
74 file,
75 e
76 );
77 continue;
78 }
79
80 let deleted_at =
81 Utc::now() + chrono::Duration::from_std(duration).map_err(|e| e.to_string())?;
82
83 let safe_file = SafeFile {
84 original_path: path.clone(),
85 moved_path: moved_path.clone(),
86 deleted_at,
87 };
88 storage.add_file(safe_file);
89
90 println!(
91 "{} '{}' {}",
92 "Moved".blue(),
93 file.green(),
94 format!(
95 "to safe storage. It will be deleted at {}",
96 deleted_at.with_timezone(&chrono::Local)
97 )
98 .blue()
99 );
100 }
101
102 storage.save_metadata()?;
103
104 Ok(())
105}
106
107pub async fn restore_command(files: Vec<String>, restore_all: bool) -> Result<(), String> {
108 let mut storage = StorageManager::new()?;
109
110 storage.cleanup()?;
112
113 let files_to_remove = if restore_all {
114 storage
115 .get_safe_files()
116 .iter()
117 .map(|f| {
118 f.moved_path
119 .file_name()
120 .unwrap()
121 .to_string_lossy()
122 .to_string()
123 })
124 .collect()
125 } else {
126 files
127 };
128
129 for file_name in files_to_remove.iter() {
131 let safe_file = match storage.find_safe_file(file_name) {
132 Some(f) => f.clone(),
133 None => {
134 eprintln!(
135 "{}: File not found in safe storage: {}",
136 "Error".red(),
137 file_name
138 );
139 continue;
140 }
141 };
142
143 if let Some(parent) = safe_file.original_path.parent() {
145 if !parent.exists() {
146 if let Err(e) = fs::create_dir_all(parent) {
147 eprintln!(
148 "{}: Failed to create parent directory for '{}': {}",
149 "Error".red(),
150 file_name,
151 e
152 );
153 continue;
154 }
155 }
156 }
157
158 if let Err(e) = fs::rename(&safe_file.moved_path, &safe_file.original_path) {
160 eprintln!(
161 "{}: Failed to restore '{}' from safe storage: {}",
162 "Error".red(),
163 file_name,
164 e
165 );
166 continue;
167 }
168
169 println!(
170 "{} '{}' {} '{}'",
171 "Restored".green(),
172 file_name.blue(),
173 "to".green(),
174 safe_file.original_path.display().to_string().blue()
175 );
176
177 storage.remove_file(&safe_file.moved_path);
179 }
180
181 storage.save_metadata()?;
182
183 Ok(())
184}
185
186pub async fn list_command() -> Result<(), String> {
187 let mut storage = StorageManager::new()?;
188
189 storage.cleanup()?;
191
192 storage.list_files();
193
194 Ok(())
195}
196
197pub async fn clean_command(force: bool) -> Result<(), String> {
198 let mut storage = StorageManager::new()?;
199 if force {
200 storage.cleanup_all_files()?;
201 } else {
202 storage.cleanup()?;
203 }
204
205 Ok(())
206}
207
208pub async fn view_command(files: Vec<String>) -> Result<(), String> {
209 let storage = StorageManager::new()?;
210
211 for file_name in files.iter() {
212 let safe_file = match storage.find_safe_file(file_name) {
213 Some(f) => f,
214 None => {
215 eprintln!(
216 "{}: File not found in safe storage: {}",
217 "Error".red(),
218 file_name
219 );
220 continue;
221 }
222 };
223
224 if !safe_file.moved_path.exists() {
225 eprintln!(
226 "{}: File not found in safe storage: {}",
227 "Error".red(),
228 file_name
229 );
230 continue;
231 }
232
233 let view_cmd = if is_bat_installed() {
234 "bat"
235 } else {
236 println!(
237 "{}: bat is not installed. Using 'cat' instead.",
238 "Warning".yellow()
239 );
240 "cat"
241 };
242
243 let status = Command::new(view_cmd)
244 .arg(&safe_file.moved_path)
245 .status()
246 .map_err(|e| e.to_string())?;
247
248 if !status.success() {
249 eprintln!(
250 "{}: Failed to view file '{}': bat exited with status {}",
251 "Error".red(),
252 file_name,
253 status
254 );
255 }
256 }
257
258 Ok(())
259}
260
261fn is_bat_installed() -> bool {
262 match Command::new("bat")
263 .arg("--version")
264 .stdout(Stdio::null())
265 .stderr(Stdio::null())
266 .status()
267 {
268 Ok(status) => status.success(),
269 Err(e) => false,
270 }
271}