champ/
fs.rs

1// Annotation Metadata Protocol
2
3use crate::{config, fail, ignore, path, util};
4use std::{ffi, fs};
5use tracing::{info, trace, warn};
6
7pub struct Node {
8    path: path::Path,
9    name: String,
10}
11impl Node {
12    fn new(path: path::Path) -> Node {
13        Node {
14            path,
15            name: String::new(),
16        }
17    }
18}
19
20#[derive(Debug)]
21pub struct GroveSpec {
22    base: path::Path,
23    // &todo: Enable removal of hidden files/folders
24    pub hidden: bool,
25    // &todo: Enable removal of ignored files/folders
26    pub ignore: bool,
27    // We assume a limited amount of extensions (less than 64): linear search is faster than using a BTreeSet
28    pub include: Vec<ffi::OsString>,
29    // &todo: Enable removal of large files
30    pub max_size: Option<usize>,
31}
32
33impl GroveSpec {
34    fn call(&self, path: &path::Path) -> bool {
35        let includes_base = self.base.include(path) || path.include(&self.base);
36        if !includes_base {
37            return false;
38        }
39        if self.include.is_empty() {
40            // No extensions were specified
41            return true;
42        }
43        if let Some(ext) = path.extension() {
44            self.include.iter().position(|e| e == ext).is_some()
45        } else {
46            // path has no extension: we only continue when it is a folder
47            path.is_folder()
48        }
49    }
50}
51
52impl From<&config::Grove> for GroveSpec {
53    fn from(config_grove: &config::Grove) -> GroveSpec {
54        GroveSpec {
55            base: path::Path::folder(&config_grove.path),
56            hidden: config_grove.hidden,
57            ignore: config_grove.ignore,
58            include: config_grove
59                .include
60                .iter()
61                .map(ffi::OsString::from)
62                .collect(),
63            max_size: config_grove.max_size,
64        }
65    }
66}
67
68pub struct Forest {
69    specs: Vec<GroveSpec>,
70    ignore_tree: ignore::Tree,
71}
72
73impl Forest {
74    pub fn new() -> Forest {
75        Forest {
76            specs: Vec::new(),
77            ignore_tree: ignore::Tree::new(),
78        }
79    }
80
81    pub fn add_grove(&mut self, forest_spec: GroveSpec) {
82        info!("amp.Forest.set_forest({})", &forest_spec.base);
83        self.specs.push(forest_spec);
84    }
85
86    pub fn list(&mut self, path: &path::Path) -> util::Result<Vec<path::Path>> {
87        trace!("amp.Forest.list({})", &path);
88
89        let mut paths = Vec::new();
90
91        if path.is_folder() {
92            self.ignore_tree
93                .with_filter(path, |filter: &ignore::Filter| {
94                    let mut entries =
95                        std::fs::read_dir(&path.path_buf())?.collect::<Result<Vec<_>, _>>()?;
96                    entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
97
98                    // Process each file/folder in path
99                    for entry in entries {
100                        if let Ok(file_type) = entry.file_type() {
101                            let new_path;
102
103                            if file_type.is_dir() {
104                                new_path = Some(path.push_clone(path::Part::Folder {
105                                    name: entry.file_name().into(),
106                                }));
107                            } else if file_type.is_file() {
108                                new_path = Some(path.push_clone(path::Part::File {
109                                    name: entry.file_name().into(),
110                                }));
111                            } else if file_type.is_symlink() {
112                                match fs::metadata(entry.path()) {
113                                    Err(err) => {
114                                        warn!(
115                                            "Warning: Skipping {}, could not read metadata: {}",
116                                            entry.path().display(),
117                                            &err
118                                        );
119                                        new_path = None;
120                                    }
121                                    Ok(mut metadata) => {
122                                        if metadata.is_symlink() {
123                                            metadata = fs::symlink_metadata(entry.path())?;
124                                        }
125                                        if metadata.is_dir() {
126                                            new_path = Some(path.push_clone(path::Part::Folder {
127                                                name: entry.file_name().into(),
128                                            }));
129                                        } else if metadata.is_file() {
130                                            new_path = Some(path.push_clone(path::Part::File {
131                                                name: entry.file_name().into(),
132                                            }));
133                                        } else {
134                                            new_path = None;
135                                        }
136                                    }
137                                }
138                            } else {
139                                warn!("I cannot handle '{}'", entry.file_name().to_string_lossy());
140                                // Something else
141                                new_path = None;
142                            }
143
144                            if let Some(new_path) = new_path {
145                                if filter.call(&new_path)
146                                    && self.specs.iter().any(|spec| spec.call(&new_path))
147                                {
148                                    paths.push(new_path);
149                                }
150                            }
151                        }
152                    }
153                    Ok(())
154                })?;
155        }
156
157        Ok(paths)
158    }
159}
160
161pub fn expand_path(path: &std::path::Path) -> util::Result<std::path::PathBuf> {
162    let mut res = std::path::PathBuf::new();
163    let mut first = true;
164    for component in path.components() {
165        trace!("component: {:?}", &component);
166        match component {
167            std::path::Component::Prefix(prefix) => {
168                res.push(prefix.as_os_str());
169                first = false;
170            }
171            std::path::Component::RootDir => {
172                res.push("/");
173                first = false;
174            }
175            std::path::Component::CurDir => {
176                if first {
177                    res.push(std::env::current_dir()?);
178                    first = false;
179                }
180            }
181            std::path::Component::Normal(normal) => {
182                if first {
183                    res.push(std::env::current_dir()?);
184                    first = false;
185                }
186                res.push(normal);
187            }
188            std::path::Component::ParentDir => {
189                fail!("No support for '..' in root dir yet");
190            }
191        }
192    }
193    Ok(res)
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_list() -> util::Result<()> {
202        let home_dir = std::env::var("HOME")?;
203
204        let mut forest = Forest::new();
205        forest.add_grove(GroveSpec {
206            base: path::Path::folder(&home_dir),
207            hidden: true,
208            ignore: true,
209            include: Vec::new(),
210            max_size: None,
211        });
212        let path = path::Path::folder(&home_dir);
213        let paths = forest.list(&path)?;
214        for p in &paths {
215            println!("{}", p);
216        }
217        Ok(())
218    }
219}