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
|
use std::{
fs::Metadata,
time::{Duration, SystemTime},
};
use camino::{Utf8Path, Utf8PathBuf};
// CSV definition:
// a row may contain any data, but comma are escaped with a backslash
// and blackslash are escaped, too. there is no header. if a time
// cannot be attained due to an error, it is left blank
// data is in the format: path, creation-time, modification-time
fn main() {
// currently only accepts one argument, the path to the directory it will
// recurse through and make the csv for.
// spits the csv on stdout
let path: Utf8PathBuf = std::env::args().nth(1).unwrap().parse().unwrap();
// behavior:
// reads directories twice. we're trying to be memory efficient rather than
// computationally. i'm not quite sure why.
// first pass: read and output file information
// second pass: read and outut directory information
process(path)
}
fn process(path: Utf8PathBuf) {
//TODO: do not panic, please
let root_meta = std::fs::metadata(&path).unwrap();
let root_times = Times::metadata(&root_meta);
row(&path, &root_times);
for entry in path.read_dir_utf8().unwrap() {
let entry = entry.unwrap();
match entry.file_type() {
Err(_) => panic!(),
Ok(ft) if ft.is_file() => {
let meta = entry.metadata().unwrap();
let times = Times::metadata(&meta);
row(entry.path(), ×)
}
Ok(_) => {}
}
}
for entry in path.read_dir_utf8().unwrap() {
let entry = entry.unwrap();
match entry.file_type() {
Err(_) => panic!(),
Ok(ft) if ft.is_dir() => process(entry.into_path()),
Ok(_) => {}
}
}
}
fn row<P: AsRef<Utf8Path>>(path: P, times: &Times) {
println!(
"{},{},{},{}",
escape(path.as_ref()),
times
.created()
.map(|d| d.as_secs().to_string())
.unwrap_or_default(),
times
.modified()
.map(|d| d.as_secs().to_string())
.unwrap_or_default(),
times
.accessed()
.map(|d| d.as_secs().to_string())
.unwrap_or_default()
);
}
fn escape<S: AsRef<str>>(raw: S) -> String {
let raw = raw.as_ref();
let mut escaped = String::with_capacity(raw.len());
for c in raw.chars() {
match c {
',' | '\\' => {
escaped.push('\\');
escaped.push(c);
}
_ => escaped.push(c),
}
}
escaped
}
struct Times {
created: Option<SystemTime>,
modified: Option<SystemTime>,
accessed: Option<SystemTime>,
}
impl Times {
pub fn metadata(meta: &Metadata) -> Self {
Self {
created: meta.created().ok(),
modified: meta.modified().ok(),
accessed: meta.accessed().ok(),
}
}
// EPOCH created
pub fn created(&self) -> Option<Duration> {
self.created
.map(|st| st.duration_since(SystemTime::UNIX_EPOCH).unwrap())
}
pub fn modified(&self) -> Option<Duration> {
self.modified
.map(|st| st.duration_since(SystemTime::UNIX_EPOCH).unwrap())
}
pub fn accessed(&self) -> Option<Duration> {
self.accessed
.map(|st| st.duration_since(SystemTime::UNIX_EPOCH).unwrap())
}
}
|