1use crate::decode::raw_to_string;
2use log::debug;
3use path_absolutize::*;
4use std::ffi::OsString;
5pub use std::fs::*;
6use std::path::{Path, PathBuf};
7use std::{fs, io};
8
9pub struct PathWalker {
10 path: Vec<PathBuf>,
11 files: Vec<PathBuf>,
12 recursive: bool,
13 allow_symlink_file: bool,
15 ignores: Vec<OsString>,
16}
17
18impl Iterator for PathWalker {
19 type Item = PathBuf;
20
21 fn next(&mut self) -> Option<Self::Item> {
22 if self.files.is_empty() {
23 if self.path.is_empty() || !self.recursive {
24 return None;
25 }
26 while self.files.is_empty() && !self.path.is_empty() {
27 self.extract_path();
28 }
29 if self.files.is_empty() {
30 return None;
31 }
32 }
33
34 Some(self.files.remove(0))
35 }
36}
37
38impl PathWalker {
39 fn extract_path(&mut self) {
40 if self.recursive && !self.path.is_empty() {
41 let path = self.path.get(0).unwrap();
42 let mut dir: Vec<_> = read_dir(path).unwrap().map(|r| r.unwrap()).collect();
43 dir.sort_by_key(|e| e.path());
44 for entry in dir.iter() {
45 let metadata = entry.metadata().unwrap();
46 if self.ignores.contains(&entry.file_name()) {
47 continue;
48 }
49
50 if metadata.is_dir() {
51 self.path.push(entry.path());
52 } else if metadata.is_file() {
53 self.files.push(entry.path());
54 } else {
55 if self.allow_symlink_file {
57 if fs::metadata(entry.path()).unwrap().is_file() {
59 self.files.push(entry.path());
60 }
61 }
62 }
63 }
64 self.path.remove(0);
65 }
66 }
67
68 pub fn new<P: AsRef<Path>>(
69 p: P,
70 recursive: bool,
71 allow_symlink_file: bool,
72 ignores: Vec<String>,
73 ) -> Self {
74 let mut path = Vec::new();
75 let mut files = Vec::new();
76 if is_dir(&p).unwrap() {
77 path.push(p.as_ref().to_owned());
78 } else {
79 files.push(p.as_ref().to_owned());
80 }
81 let mut walker = PathWalker {
82 path,
83 files,
84 recursive: true,
85 allow_symlink_file,
86 ignores: ignores.into_iter().map(|s| s.into()).collect(),
87 };
88 walker.extract_path();
89 walker.recursive = recursive;
90 walker
91 }
92
93 pub fn with_extensions(extensions: Box<[&str]>) -> Box<dyn Fn(&PathBuf) -> bool + '_> {
94 Box::new(move |file: &PathBuf| match file.extension() {
95 None => false,
96 Some(ext) => extensions.contains(&ext.to_str().unwrap()),
97 })
98 }
99}
100
101fn fs_walk_path<P: AsRef<Path>>(
102 path: P,
103 recursive: bool,
104 callback: &impl Fn(&Path) -> bool,
105) -> io::Result<bool> {
106 let meta = metadata(&path)?;
107 if meta.is_dir() && recursive {
108 let mut dir: Vec<_> = read_dir(path)?.map(|r| r.unwrap().path()).collect();
109 dir.sort();
110 for entry in dir {
111 if !fs_walk_path(entry, recursive, callback)? {
112 return Ok(false);
113 }
114 }
115 Ok(true)
116 } else {
117 Ok(callback(path.as_ref()))
118 }
119}
120
121pub fn walk_path<P: AsRef<Path>>(
122 path: P,
123 recursive: bool,
124 callback: impl Fn(&Path) -> bool,
125) -> io::Result<()> {
126 let _ = fs_walk_path(path, recursive, &callback)?;
127 Ok(())
128}
129
130pub fn is_dir<P: AsRef<Path>>(path: P) -> io::Result<bool> {
131 let meta = metadata(path.as_ref())?;
132 Ok(meta.is_dir())
133}
134
135pub fn get_ext_files<P: AsRef<Path>, T: AsRef<str>>(
136 dir: P,
137 ext: T,
138 recursive: bool,
139) -> io::Result<Vec<PathBuf>> {
140 let mut result = Vec::new();
141 if is_dir(dir.as_ref())? {
142 for file in PathWalker::new(dir.as_ref(), recursive, true, Default::default()) {
143 let file_ext = file
144 .extension()
145 .unwrap_or_default()
146 .to_str()
147 .unwrap_or_default();
148 if file_ext == ext.as_ref() {
149 result.push(file);
150 }
151 }
152 }
153 Ok(result)
154}
155
156pub fn get_ext_file<P: AsRef<Path>, T: AsRef<str>>(
157 dir: P,
158 ext: T,
159 recursive: bool,
160) -> io::Result<Option<PathBuf>> {
161 if is_dir(dir.as_ref())? {
162 for file in PathWalker::new(dir.as_ref(), recursive, true, Default::default()) {
163 let file_ext = file
164 .extension()
165 .unwrap_or_default()
166 .to_str()
167 .unwrap_or_default();
168 if file_ext == ext.as_ref() {
169 return Ok(Some(file));
170 }
171 }
172 }
173 Ok(None)
174}
175
176pub fn get_subdirectories<P: AsRef<Path>>(dir: P) -> io::Result<Vec<PathBuf>> {
177 let mut ret = Vec::new();
178 let mut dir: Vec<_> = read_dir(dir.as_ref())?.map(|r| r.unwrap()).collect();
179 dir.sort_by_key(|e| e.path());
180 for dir in dir.iter() {
181 let dir_type = dir.file_type()?;
182 if dir_type.is_dir() {
183 ret.push(dir.path());
184 }
185 }
186 Ok(ret)
187}
188
189pub fn read_to_string<P: AsRef<Path>>(input: P) -> io::Result<String> {
190 log::trace!("Reading file to string: {:?}", input.as_ref());
191 let r = read(input)?;
192 Ok(raw_to_string(&r))
193}
194
195#[cfg(feature = "trash")]
196pub fn remove_file<P: AsRef<Path>>(input: P, trashcan: bool) -> io::Result<()> {
197 if trashcan {
198 trash::delete(input.as_ref()).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
199 } else {
200 fs::remove_file(input)
201 }
202}
203
204#[cfg(feature = "trash")]
205pub fn remove_dir_all<P: AsRef<Path>>(path: P, trashcan: bool) -> io::Result<()> {
206 if trashcan {
207 trash::delete(path).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
208 } else {
209 fs::remove_dir_all(path)
210 }
211}
212
213pub fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
215 let link = path_diff(from, to.as_ref().parent().unwrap())?;
216 #[cfg(unix)]
217 return std::os::unix::fs::symlink(link, to);
218 #[cfg(windows)]
219 return std::os::windows::fs::symlink_file(link, to);
220}
221
222pub fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
224 let link = path_diff(from, to.as_ref().parent().unwrap())?;
225 #[cfg(unix)]
226 return std::os::unix::fs::symlink(link, to);
227 #[cfg(windows)]
228 return std::os::windows::fs::symlink_dir(link, to);
229}
230
231pub fn path_diff<P: AsRef<Path>, Q: AsRef<Path>>(path: P, base: Q) -> io::Result<PathBuf> {
232 Ok(pathdiff::diff_paths(path.as_ref().absolutize()?, base.as_ref().absolutize()?).unwrap())
233}
234
235pub fn copy_dir<P1, P2>(from: P1, to: P2) -> io::Result<()>
236where
237 P1: AsRef<Path>,
238 P2: AsRef<Path>,
239{
240 create_dir(to.as_ref())?;
241
242 for entry in read_dir(from)? {
243 let entry = entry?;
244 let file_type = entry.file_type()?;
245 let target = to.as_ref().join(entry.file_name());
246 if file_type.is_file() {
247 copy(entry.path(), target)?;
248 } else if file_type.is_dir() {
249 copy_dir(entry.path(), target)?;
250 }
251 }
252
253 Ok(())
254}
255
256pub fn move_dir<P1, P2>(from: P1, to: P2) -> io::Result<()>
261where
262 P1: AsRef<Path>,
263 P2: AsRef<Path>,
264{
265 if !is_dir(from.as_ref())? {
267 return Err(io::Error::new(
268 io::ErrorKind::InvalidInput,
269 format!("{} is not a directory", from.as_ref().display()),
270 ));
271 }
272
273 match rename(from.as_ref(), to.as_ref()) {
274 Err(e) if is_cross_device_error(&e) => {
275 debug!("Failed to rename across filesystems. Copying instead.");
276
277 copy_dir(from.as_ref(), to.as_ref())?;
278 debug!("Copying done. Removing source directory.");
279
280 fs::remove_dir_all(from.as_ref())?;
281 debug!("Source directory removed.");
282 }
283 _ => {}
284 };
285
286 Ok(())
287}
288
289fn is_cross_device_error(error: &io::Error) -> bool {
294 let code = error.raw_os_error();
295 #[cfg(windows)]
296 {
297 code == Some(17)
298 }
299 #[cfg(unix)]
300 {
301 code == Some(18)
302 }
303 #[cfg(all(not(windows), not(unix)))]
304 {
305 false
307 }
308}