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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#![warn(missing_docs)]
use walkdir::WalkDir;
use std::path::Path;
use std::io;
use std::fs;
use std::fmt;
use std::error::Error;
pub enum UpdateError {
InvalidFrom,
InvalidTo,
Io(io::Error),
}
impl Error for UpdateError {
fn description(&self) -> &str {
match self {
UpdateError::InvalidFrom => "Directory from which to copy files doesn't exist or cannot be accessed",
UpdateError::InvalidTo => "Directory into which to copy files doesn't exist or cannot be accessed",
UpdateError::Io(_) => "I/O Error occurred",
}
}
}
impl fmt::Debug for UpdateError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
UpdateError::Io(ref error) => write!(f, "I/O Error occurred: {}", error),
_ => write!(f, "{}", self.description()),
}
}
}
impl fmt::Display for UpdateError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
fn skip_hidden(entry: &walkdir::DirEntry) -> bool {
!entry.file_name().to_str().map(|s| s.starts_with(".")).unwrap_or(false)
}
pub trait DirUpdate {
#[inline]
fn on_new_file_update(_file: &Path) {
}
#[inline]
fn on_new_file_skip(_file: &Path) {
}
fn on_io_error(file: &Path, error: io::Error) {
eprintln!("{}: Error accessing: {}", file.display(), error);
}
fn on_walk_error(error: walkdir::Error) {
match (error.path(), error.io_error()) {
(Some(path), Some(error)) => eprintln!("{}: Cannot access file. Error: {}", path.display(), error),
(Some(path), None) => eprintln!("{}: Cannot access file", path.display()),
(None, Some(error)) => eprintln!("I/O Error: {}", error),
(None, None) => eprintln!("Unknown error happened"),
}
}
#[doc(hidden)]
fn filter_error(value: walkdir::Result<walkdir::DirEntry>) -> Option<walkdir::DirEntry> {
match value {
Ok(entry) => match entry.file_type().is_dir() {
true => None,
false => Some(entry),
},
Err(error) => {
Self::on_walk_error(error);
None
}
}
}
fn update_dir(from: &Path, to: &Path) -> Result<usize, UpdateError> {
if !from.metadata().map(|from| from.is_dir()).unwrap_or(false) {
return Err(UpdateError::InvalidFrom);
} else if !to.metadata().map(|to| to.is_dir()).unwrap_or(false) {
return Err(UpdateError::InvalidTo);
}
let mut result = 0;
for to_entry in WalkDir::new(to).into_iter().filter_entry(skip_hidden).filter_map(Self::filter_error) {
let to_path = to_entry.path();
let to_short_path = to_path.strip_prefix(to).expect("To strip prefix of file in to directory");
let from_path = from.join(to_short_path);
let mut from_file = match fs::File::open(&from_path) {
Ok(file) => file,
Err(_) => continue
};
let from_size = match from_file.metadata() {
Ok(meta) => meta.len(),
Err(error) => {
Self::on_io_error(&from_path, error);
continue;
}
};
let to_size = match to_path.metadata() {
Ok(meta) => meta.len(),
Err(error) => {
Self::on_io_error(&to_path, error);
continue;
}
};
if to_size == from_size {
Self::on_new_file_skip(&to_path);
continue
}
let mut to_file = match fs::File::create(&to_path) {
Ok(file) => file,
Err(error) => {
Self::on_io_error(&to_path, error);
continue;
}
};
match io::copy(&mut from_file, &mut to_file) {
Ok(_) => {
Self::on_new_file_update(&to_path);
result += 1;
},
Err(error) => Self::on_io_error(to_path, error),
}
}
Ok(result)
}
}