about summary refs log tree commit diff
path: root/src/main.rs
blob: b62247e15a893fab9990e07959231a51781042f5 (plain)
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(), &times)
			}
			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())
	}
}