1use 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 pub hidden: bool,
25 pub ignore: bool,
27 pub include: Vec<ffi::OsString>,
29 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 return true;
42 }
43 if let Some(ext) = path.extension() {
44 self.include.iter().position(|e| e == ext).is_some()
45 } else {
46 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 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 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}