~iptq/garbage

ref: 02ba29053be29149ec4ca4f73cd375f02f2cf21b garbage/src/dir.rs -rw-r--r-- 3.9 KiB
02ba2905Michael Zhang fix panic problem by not converting to utf-8 all the time 1 year, 3 months ago
                                                                                
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
use std::ffi::OsStr;
use std::fs;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};

use walkdir::{DirEntry, WalkDir};

use crate::Error;
use crate::TrashInfo;
use crate::XDG;

/// A trash directory represented by a path.
#[derive(Clone, Debug)]
pub struct TrashDir(pub PathBuf);

impl TrashDir {
    /// Constructor for a new trash directory.
    pub fn from(path: impl AsRef<Path>) -> Self {
        TrashDir(path.as_ref().to_path_buf())
    }

    /// Gets your user's "home" trash directory.
    ///
    /// According to Trash spec v1.0:
    ///
    /// > For every user2 a “home trash” directory MUST be available.
    /// > Its name and location are $XDG_DATA_HOME/Trash
    /// > $XDG_DATA_HOME is the base directory for user-specific data, as defined in the Desktop Base Directory Specification.
    pub fn get_home_trash() -> Self {
        TrashDir::from(XDG.get_data_home().join("Trash"))
    }

    /// Create a trash directory from an optional path
    ///
    /// If the option is None, then the home trash will be selected instead.
    pub fn from_opt(opt: Option<impl AsRef<Path>>) -> Self {
        opt.map(|path| TrashDir::from(path.as_ref().to_path_buf()))
            .unwrap_or_else(|| TrashDir::get_home_trash())
    }

    /// Actually create the directory on disk corresponding to this trash directory
    pub fn create(&self) -> Result<(), Error> {
        let path = &self.0;
        if !path.exists() {
            fs::create_dir_all(&path)?;
        }
        Ok(())
    }

    /// Returns the path to this trash directory.
    pub fn path(&self) -> &Path {
        self.0.as_ref()
    }

    /// Get the `files` directory
    pub fn files_dir(&self) -> Result<PathBuf, Error> {
        let target = self.0.join("files");
        if !target.exists() {
            fs::create_dir_all(&target)?;
        }
        Ok(target)
    }

    /// Get the `info` directory
    pub fn info_dir(&self) -> Result<PathBuf, Error> {
        let target = self.0.join("info");
        if !target.exists() {
            fs::create_dir_all(&target)?;
        }
        Ok(target)
    }

    /// Get the `info` directory
    pub fn check_info_dir(&self) -> Result<Option<PathBuf>, Error> {
        let target = self.0.join("info");
        if !target.exists() {
            Ok(None)
        } else {
            Ok(Some(target))
        }
    }

    /// Iterate over trash infos within this trash directory
    pub fn iter(&self) -> Result<TrashDirIter, Error> {
        let iter = WalkDir::new(&self.info_dir()?)
            .contents_first(true)
            .into_iter()
            .filter_entry(|entry| match entry.path().extension() {
                Some(x) => x == "trashinfo",
                _ => false,
            });
        Ok(TrashDirIter(self.0.clone(), Box::new(iter)))
    }
}

pub struct TrashDirIter(PathBuf, Box<dyn Iterator<Item = walkdir::Result<DirEntry>>>);

impl Iterator for TrashDirIter {
    type Item = Result<TrashInfo, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        let entry = {
            let mut entry;
            loop {
                entry = match self.1.next() {
                    Some(Ok(entry)) => entry,
                    Some(Err(err)) => return Some(Err(Error::from(err))),
                    None => return None,
                };
                if entry.path().is_dir() {
                    continue;
                }
                break;
            }
            entry
        };

        let name = match entry.path().file_name() {
            Some(name) => name,
            None => return None,
        };
        let deleted_path = if !name.as_bytes().ends_with(b".trashinfo") {
            return self.next();
        } else {
            self.0.join("files").join(OsStr::from_bytes(
                &name.as_bytes()[..name.len() - b".trashinfo".len()],
            ))
        };
        Some(TrashInfo::from_files(entry.path(), deleted_path).map_err(Error::from))
    }
}