safe_remove/
commands.rs

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), // 7 days
33    };
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        // Handle duplicate file names
56        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    // Perform cleanup before restoring
111    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    // Process each file before restoring
130    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        // Ensure parent directory exists
144        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        // Restore the file
159        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        // Remove the file from the safe storage metadata
178        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    // Perform cleanup before listing
190    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}