1mod parser;
2pub mod percent_path;
3pub mod trash_entry;
4pub mod trash_info;
5mod utils;
6
7use std::io;
8use std::path::{Path, PathBuf};
9
10use directories::{ProjectDirs, UserDirs};
11use fs_extra::dir;
12use fs_extra::file;
13use log::{error, warn};
14use once_cell::sync::Lazy;
15use snafu::{ResultExt, Snafu};
16
17use trash_entry::{read_dir_trash_entries, TrashEntry};
18use utils::{read_dir_path, remove_path};
19
20static USER_DIRS: Lazy<UserDirs> =
21 Lazy::new(|| UserDirs::new().expect("Failed to determine user directories."));
22static HOME_DIR: Lazy<&Path> = Lazy::new(|| &USER_DIRS.home_dir());
23pub static TRASH_DIR: Lazy<PathBuf> = Lazy::new(|| HOME_DIR.join(".local/share/Trash"));
24pub static TRASH_INFO_DIR: Lazy<PathBuf> = Lazy::new(|| TRASH_DIR.join("info"));
25pub static TRASH_FILE_DIR: Lazy<PathBuf> = Lazy::new(|| TRASH_DIR.join("files"));
26pub const TRASH_INFO_EXT: &'_ str = "trashinfo";
27pub const FILE_COPY_OPT: file::CopyOptions = file::CopyOptions {
28 overwrite: true,
29 skip_exist: false,
30 buffer_size: 64000,
31};
32pub const DIR_COPY_OPT: dir::CopyOptions = dir::CopyOptions {
33 overwrite: true,
34 skip_exist: false,
35 buffer_size: 64000,
36 copy_inside: false,
37 content_only: false,
38 depth: 0,
39};
40
41#[derive(Debug, Snafu)]
42pub enum Error {
43 #[snafu(display("Failed to create new trash entry struct"))]
44 TrashEntryNew { source: trash_entry::Error },
45
46 #[snafu(display(
47 "Failed to create a new trash entry by moving a file and creating a trash info file"
48 ))]
49 TrashEntryCreation { source: trash_entry::Error },
50
51 #[snafu(display("Failed to read an iterator of trash entries"))]
52 ReadDirTrashEntry { source: trash_entry::Error },
53
54 #[snafu(display("Failed to restore trash entry"))]
55 TrashEntryRestore { source: trash_entry::Error },
56
57 #[snafu(display("Failed to remove trash entry"))]
58 TrashEntryRemove { source: trash_entry::Error },
59
60 #[snafu(context(false))]
61 #[snafu(display("Utils error"))]
62 Utils { source: utils::Error },
63
64 #[snafu(context(false))]
65 ReadDirPath { source: io::Error },
66
67 #[snafu(display("The stray path {} was found that could not be made into a trash entry", path.display()))]
68 StrayPath { path: PathBuf },
69}
70
71type Result<T, E = Error> = ::std::result::Result<T, E>;
72
73#[macro_export]
74macro_rules! ok_log {
75 ($res:expr => $log_macro:ident!) => {
76 match $res {
77 Ok(t) => Some(t),
78 Err(e) => {
79 $log_macro!("{}", e);
80 None
81 }
82 }
83 };
84 ($res:expr => $print_func:ident) => {
85 match $res {
86 Ok(t) => Some(t),
87 Err(e) => {
88 $print_func(format!("{}", e));
89 None
90 }
91 }
92 };
93}
94
95pub fn restore(name: impl AsRef<Path>) -> Result<()> {
97 Ok(TrashEntry::new(name)
98 .context(TrashEntryNew)?
99 .restore()
100 .context(TrashEntryRestore)?)
101}
102
103pub fn remove(name: impl AsRef<Path>) -> Result<()> {
105 Ok(TrashEntry::new(name)
106 .context(TrashEntryNew)?
107 .remove()
108 .context(TrashEntryRemove)?)
109}
110
111pub fn empty(keep_stray: bool) -> Result<()> {
113 read_dir_trash_entries()
115 .context(ReadDirTrashEntry)?
116 .map(|trash_entry| trash_entry.remove().context(TrashEntryRemove))
117 .filter_map(|res| ok_log!(res => error!))
118 .for_each(|_| ());
119
120 if !keep_stray {
122 read_dir_path(&TRASH_INFO_DIR)?
123 .chain(read_dir_path(&TRASH_FILE_DIR)?)
124 .inspect(|path| warn!("{}", StrayPath { path }.build()))
125 .map(|path| remove_path(path))
126 .filter_map(|res| ok_log!(res => error!))
127 .for_each(|_| ());
128 }
129
130 Ok(())
131}
132
133pub fn put(paths: &[impl AsRef<Path>]) -> Result<Vec<TrashEntry>> {
136 if paths.is_empty() {
137 panic!("Attempting to put empty paths");
138 }
139
140 let mut existing: Vec<_> = read_dir_trash_entries()
141 .context(ReadDirTrashEntry)?
142 .collect();
143 let old_trash_entries_end = existing.len() - 1;
144
145 for path in paths {
146 let trash_entry = TrashEntry::create(path, &existing).context(TrashEntryCreation)?;
147 existing.push(trash_entry)
148 }
149
150 existing.drain(..old_trash_entries_end);
151
152 Ok(existing)
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use anyhow::{Context, Result};
159 use std::fs::File;
160 use std::io::Write;
161 use tempfile::NamedTempFile;
162
163 #[should_panic]
164 #[test]
165 fn put_test_nothing_test() {
166 let nothing: [&str; 0] = [];
167 let _ = put(¬hing);
168 }
169
170 #[ignore]
171 #[test]
172 fn put_test_single() -> Result<()> {
173 let mut tempfile = NamedTempFile::new()?;
174 tempfile.write_all(b"this is for the put_test_single")?;
175 put(&[tempfile.path()])?;
176 Ok(())
179 }
180}