trash_lib/
lib.rs

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
95/// Helper function
96pub fn restore(name: impl AsRef<Path>) -> Result<()> {
97    Ok(TrashEntry::new(name)
98        .context(TrashEntryNew)?
99        .restore()
100        .context(TrashEntryRestore)?)
101}
102
103/// Helper function
104pub fn remove(name: impl AsRef<Path>) -> Result<()> {
105    Ok(TrashEntry::new(name)
106        .context(TrashEntryNew)?
107        .remove()
108        .context(TrashEntryRemove)?)
109}
110
111/// Removes all trash_entries and optionally removes stray files.
112pub fn empty(keep_stray: bool) -> Result<()> {
113    // remove the the correct trash_entries
114    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    // remove the stray files taht does not have a pair
121    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
133/// Put a list of paths into the trash and returns the newly created trash_entries. Will panic if the
134/// paths are empty!
135pub 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(&nothing);
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        // tempfile.close()?; // TODO: fix this failure
177
178        Ok(())
179    }
180}