1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
mod parser;
pub mod percent_path;
pub mod trash_entry;
pub mod trash_info;
mod utils;

use std::io;
use std::path::{Path, PathBuf};

use directories::{ProjectDirs, UserDirs};
use fs_extra::dir;
use fs_extra::file;
use log::{error, warn};
use once_cell::sync::Lazy;
use snafu::{ResultExt, Snafu};

use trash_entry::{read_dir_trash_entries, TrashEntry};
use utils::{read_dir_path, remove_path};

static USER_DIRS: Lazy<UserDirs> =
    Lazy::new(|| UserDirs::new().expect("Failed to determine user directories."));
static HOME_DIR: Lazy<&Path> = Lazy::new(|| &USER_DIRS.home_dir());
pub static TRASH_DIR: Lazy<PathBuf> = Lazy::new(|| HOME_DIR.join(".local/share/Trash"));
pub static TRASH_INFO_DIR: Lazy<PathBuf> = Lazy::new(|| TRASH_DIR.join("info"));
pub static TRASH_FILE_DIR: Lazy<PathBuf> = Lazy::new(|| TRASH_DIR.join("files"));
pub const TRASH_INFO_EXT: &'_ str = "trashinfo";
pub const FILE_COPY_OPT: file::CopyOptions = file::CopyOptions {
    overwrite: true,
    skip_exist: false,
    buffer_size: 64000,
};
pub const DIR_COPY_OPT: dir::CopyOptions = dir::CopyOptions {
    overwrite: true,
    skip_exist: false,
    buffer_size: 64000,
    copy_inside: false,
    content_only: false,
    depth: 0,
};

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Failed to create new trash entry struct"))]
    TrashEntryNew { source: trash_entry::Error },

    #[snafu(display(
        "Failed to create a new trash entry by moving a file and creating a trash info file"
    ))]
    TrashEntryCreation { source: trash_entry::Error },

    #[snafu(display("Failed to read an iterator of trash entries"))]
    ReadDirTrashEntry { source: trash_entry::Error },

    #[snafu(display("Failed to restore trash entry"))]
    TrashEntryRestore { source: trash_entry::Error },

    #[snafu(display("Failed to remove trash entry"))]
    TrashEntryRemove { source: trash_entry::Error },

    #[snafu(context(false))]
    #[snafu(display("Utils error"))]
    Utils { source: utils::Error },

    #[snafu(context(false))]
    ReadDirPath { source: io::Error },

    #[snafu(display("The stray path {} was found that could not be made into a trash entry", path.display()))]
    StrayPath { path: PathBuf },
}

type Result<T, E = Error> = ::std::result::Result<T, E>;

#[macro_export]
macro_rules! ok_log {
    ($res:expr => $log_macro:ident!) => {
        match $res {
            Ok(t) => Some(t),
            Err(e) => {
                $log_macro!("{}", e);
                None
            }
        }
    };
    ($res:expr => $print_func:ident) => {
        match $res {
            Ok(t) => Some(t),
            Err(e) => {
                $print_func(format!("{}", e));
                None
            }
        }
    };
}

/// Helper function
pub fn restore(name: impl AsRef<Path>) -> Result<()> {
    Ok(TrashEntry::new(name)
        .context(TrashEntryNew)?
        .restore()
        .context(TrashEntryRestore)?)
}

/// Helper function
pub fn remove(name: impl AsRef<Path>) -> Result<()> {
    Ok(TrashEntry::new(name)
        .context(TrashEntryNew)?
        .remove()
        .context(TrashEntryRemove)?)
}

/// Removes all trash_entries and optionally removes stray files.
pub fn empty(keep_stray: bool) -> Result<()> {
    // remove the the correct trash_entries
    read_dir_trash_entries()
        .context(ReadDirTrashEntry)?
        .map(|trash_entry| trash_entry.remove().context(TrashEntryRemove))
        .filter_map(|res| ok_log!(res => error!))
        .for_each(|_| ());

    // remove the stray files taht does not have a pair
    if !keep_stray {
        read_dir_path(&TRASH_INFO_DIR)?
            .chain(read_dir_path(&TRASH_FILE_DIR)?)
            .inspect(|path| warn!("{}", StrayPath { path }.build()))
            .map(|path| remove_path(path))
            .filter_map(|res| ok_log!(res => error!))
            .for_each(|_| ());
    }

    Ok(())
}

/// Put a list of paths into the trash and returns the newly created trash_entries. Will panic if the
/// paths are empty!
pub fn put(paths: &[impl AsRef<Path>]) -> Result<Vec<TrashEntry>> {
    if paths.is_empty() {
        panic!("Attempting to put empty paths");
    }

    let mut existing: Vec<_> = read_dir_trash_entries()
        .context(ReadDirTrashEntry)?
        .collect();
    let old_trash_entries_end = existing.len() - 1;

    for path in paths {
        let trash_entry = TrashEntry::create(path, &existing).context(TrashEntryCreation)?;
        existing.push(trash_entry)
    }

    existing.drain(..old_trash_entries_end);

    Ok(existing)
}

#[cfg(test)]
mod tests {
    use super::*;
    use anyhow::{Context, Result};
    use std::fs::File;
    use std::io::Write;
    use tempfile::NamedTempFile;

    #[should_panic]
    #[test]
    fn put_test_nothing_test() {
        let nothing: [&str; 0] = [];
        let _ = put(&nothing);
    }

    #[ignore]
    #[test]
    fn put_test_single() -> Result<()> {
        let mut tempfile = NamedTempFile::new()?;
        tempfile.write_all(b"this is for the put_test_single")?;
        put(&[tempfile.path()])?;
        // tempfile.close()?; // TODO: fix this failure

        Ok(())
    }
}