metadata_backup/
backup.rs1use std::io::Write;
16use std::path::{Path, PathBuf};
17
18use std::cell::RefCell;
19use std::collections::BinaryHeap;
20use std::sync::Mutex;
21
22use rayon::prelude::*;
23use walkdir::WalkDir;
24
25use super::metadata;
26
27pub static MANIFEST_FILE_NAME: &'static str = "FILE_MANIFEST";
28
29pub fn write_backup<P: AsRef<Path>>(file_root: P, out_path: P) -> Result<(), Error> {
32 let file_root = file_root.as_ref();
33 let out_path = out_path.as_ref();
34 let zip_root = PathBuf::from("FILESYSTEM_ROOT");
35
36 let directories: Vec<PathBuf> = get_all_directories(file_root)?;
38
39 let basic_options = zip::write::FileOptions::default().unix_permissions(0o755);
40 let zip_file = std::fs::File::create(out_path)?;
41 let zip_writer = Mutex::new(zip::ZipWriter::new(zip_file));
42 let file_manifest = Mutex::new(BinaryHeap::new());
43
44 thread_local! {
46 static DATA_BUFFER : RefCell<Vec<u8>> = RefCell::new(Vec::new());
47 };
48
49 let write_directory = |directory_path: &PathBuf| -> Result<(), Error> {
50 let base_dir_path = directory_path.strip_prefix(file_root)?;
51 let dir_zpath = zip_root.join(&base_dir_path);
52 let contents_path = dir_zpath.join("contents.csv");
53
54 let records = match load_directory_metadata(&directory_path) {
55 Ok(records) => records,
56 Err(e) => {
57 if is_skippable_error(&e) {
58 eprintln!("Warning: {} at path {}", e, directory_path.display());
59 return Ok(());
60 } else {
61 return Err(e.into());
62 }
63 }
64 };
65
66 let to_manifest_listing = |md: &metadata::Metadata| -> String {
68 base_dir_path
69 .join(md.name.clone())
70 .to_string_lossy()
71 .into_owned()
72 };
73
74 let mut manifest = file_manifest.lock().unwrap();
75 manifest.extend(records.iter().map(to_manifest_listing));
76
77 DATA_BUFFER.with(|buffer| -> Result<(), Error> {
79 let mut buffer = buffer.borrow_mut();
80 let mut csv_writer = csv::Writer::from_writer(&mut *buffer);
81 for record in records {
82 csv_writer.serialize(record)?;
83 }
84 Ok(())
85 })?;
86 let mut writer = zip_writer.lock().unwrap();
88
89 writer.add_directory(
90 dir_zpath
91 .to_str()
92 .ok_or(Error::new("Failed to convert directory to string"))?,
93 basic_options,
94 )?;
95
96 writer.start_file_from_path(&contents_path, basic_options)?;
97 DATA_BUFFER.with(|buffer| -> Result<(), Error> {
98 let mut buffer = buffer.borrow_mut();
99 writer.write_all(&mut *buffer)?;
100 buffer.clear();
101 Ok(())
102 })?;
103
104 Ok(())
105 };
106
107 directories.par_iter().try_for_each(write_directory)?;
108
109 let file_manifest_vec = file_manifest.into_inner().unwrap().into_sorted_vec();
111 let mut writer = zip_writer.lock().unwrap();
112 writer.start_file_from_path(&PathBuf::from(MANIFEST_FILE_NAME), basic_options)?;
113 for file_path_str in file_manifest_vec {
114 writer.write_all(&file_path_str.as_bytes())?;
115 writer.write_all(b"\n")?;
116 }
117 Ok(())
118}
119
120fn load_directory_metadata<P: AsRef<Path>>(
121 dir_path: P,
122) -> Result<Vec<metadata::Metadata>, std::io::Error> {
123 let dir_path = dir_path.as_ref();
124 std::fs::read_dir(dir_path)?
125 .into_iter()
126 .map(|entry| metadata::Metadata::new(&(entry?).path()))
127 .collect()
128}
129
130fn is_skippable_error(err: &std::io::Error) -> bool {
131 match err.kind() {
132 std::io::ErrorKind::PermissionDenied => true,
133 _ => false,
134 }
135}
136
137fn get_all_directories<P: AsRef<Path>>(base_path: P) -> Result<Vec<PathBuf>, Error> {
138 WalkDir::new(base_path.as_ref())
139 .into_iter()
140 .filter_map(|entry| match entry.as_ref() {
141 Ok(val) => {
142 let path = val.path();
143 if path.is_dir() {
144 Some(Ok(path.to_path_buf()))
145 } else {
146 None
147 }
148 }
149 Err(e) => {
150 if is_skippable_error(e.io_error()?) {
151 eprintln!("Warning: {}", e.to_string());
152 None
153 } else {
154 Some(Err(Error::new(e.to_string())))
155 }
156 }
157 })
158 .collect()
159}
160
161pub struct Error {
162 message: String,
163}
164
165impl Error {
166 pub fn new<T: Into<String>>(message: T) -> Error {
167 Error {
168 message: message.into(),
169 }
170 }
171}
172
173impl From<csv::Error> for Error {
174 fn from(err: csv::Error) -> Error {
175 Error::new(err.to_string())
176 }
177}
178
179impl From<std::path::StripPrefixError> for Error {
180 fn from(err: std::path::StripPrefixError) -> Error {
181 Error::new(err.to_string())
182 }
183}
184impl From<zip::result::ZipError> for Error {
185 fn from(err: zip::result::ZipError) -> Error {
186 Error::new(err.to_string())
187 }
188}
189
190impl From<std::io::Error> for Error {
191 fn from(err: std::io::Error) -> Error {
192 Error::new(err.to_string())
193 }
194}
195
196impl From<walkdir::Error> for Error {
197 fn from(err: walkdir::Error) -> Error {
198 Error::new(err.to_string())
199 }
200}
201
202impl std::fmt::Display for Error {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 write!(f, "{}", self.message)
205 }
206}