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
//! # Tools to handle a file system
use std::{
    fs::{DirEntry, Metadata},
    io,
    path::Path,
};

/// Information on a file
pub trait FileInfo {
    /// Return just the filename
    fn name(&self) -> &str;

    /// Return the full "local" path
    fn path(&self) -> String;

    /// Return the "real" path
    fn real(&self) -> &Path;

    /// Return the metadata for this file
    fn metadata(&self) -> io::Result<Metadata>;
}

// Join a windows path to a prefix
fn win_join(base: &str, name: &str) -> String {
    if base.is_empty() {
        name.to_string()
    } else if base.ends_with('\\') {
        format!("{}{}", base, name)
    } else {
        format!("{}\\{}", base, name)
    }
}

/// A trait to scan a directory of files
pub trait FsVisitor {
    /// Called when a file is visited
    fn visit_file<F: FileInfo>(&mut self, info: F);

    /// Called when read-dir fails
    #[allow(unused_variables)]
    fn failed_read_dir(&mut self, real: &Path, e: io::Error) {
        #[cfg(feature = "log")]
        log::error!("Failed to read_dir {}: {}", real.display(), e);
    }

    /// Called when read-dir fails
    #[allow(unused_variables)]
    fn failed_next_dir_entry(&mut self, real: &Path, e: io::Error) {
        #[cfg(feature = "log")]
        log::error!("Failed next dir entry {}: {}", real.display(), e);
    }
}

/// Information on a file used by [`scan_dir`]
struct ScanFileInfo<'a> {
    _path: &'a str,
    _name: String,
    _real: &'a Path,
    _entry: DirEntry,
}

impl<'a> FileInfo for ScanFileInfo<'a> {
    fn name(&self) -> &str {
        self._name.as_ref()
    }

    fn path(&self) -> String {
        win_join(self._path, &self._name)
    }

    fn real(&self) -> &Path {
        self._real
    }

    fn metadata(&self) -> io::Result<Metadata> {
        self._entry.metadata()
    }
}

/// Scan a directory and call [FsVisitor::visit_file] for all files
///
/// ## Parameters
///
/// - *path*: a relative path with windows-style separators (i.e `\`)
/// - *read*: the real path of the directory
/// - *recurse*: Whether to recurse into subdirectories
pub fn scan_dir<V: FsVisitor>(visitor: &mut V, path: String, real: &Path, recurse: bool) {
    let rd = match std::fs::read_dir(real) {
        Ok(rd) => rd,
        Err(e) => {
            visitor.failed_read_dir(real, e);
            return;
        }
    };

    // collect all entries
    for e in rd {
        match e {
            Ok(_entry) => {
                let new_real_path = _entry.path();
                let name = new_real_path
                    .file_name()
                    .expect("file_name on dir entry path")
                    .to_string_lossy()
                    .into_owned();
                match _entry.file_type() {
                    Ok(t) => {
                        if t.is_file() {
                            visitor.visit_file(ScanFileInfo {
                                _path: &path,
                                _name: name,
                                _real: &new_real_path,
                                _entry,
                            });
                        } else if t.is_dir() && recurse {
                            let new_path = win_join(&path, &name);
                            scan_dir(visitor, new_path, &new_real_path, recurse);
                        }
                    }
                    Err(e) => visitor.failed_next_dir_entry(&new_real_path, e),
                }
            }
            Err(e) => {
                visitor.failed_next_dir_entry(real, e);
                return;
            }
        };
    }
}