1#![allow(dead_code)]
2
3mod log;
4
5pub use crate::log::{initialize_log, write_log};
6
7use std::collections::hash_map;
8use std::ffi::OsString;
9use std::fmt;
10use std::fs::{self, File, OpenOptions};
11use std::hash::{Hash, Hasher};
12use std::io::{self, Read, Seek, SeekFrom, Write};
13use std::path::{Path, PathBuf};
14use std::str::FromStr;
15
16#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
30pub enum WriteMode {
31 AlwaysAppend,
32 AppendOrCreate,
33 CreateOrTruncate,
34 AlwaysCreate,
35 Atomic,
36}
37
38impl From<WriteMode> for OpenOptions {
39 fn from(m: WriteMode) -> OpenOptions {
40 let mut result = OpenOptions::new();
41
42 match m {
43 WriteMode::AlwaysAppend => { result.append(true); },
44 WriteMode::AppendOrCreate => { result.append(true).create(true); },
45 WriteMode::CreateOrTruncate | WriteMode::Atomic => { result.write(true).truncate(true).create(true); },
46 WriteMode::AlwaysCreate => { result.write(true).create_new(true); },
47 }
48
49 result
50 }
51}
52
53pub fn read_bytes_offset(path: &str, from: u64, to: u64) -> Result<Vec<u8>, FileError> {
57 assert!(to >= from);
58
59 match File::open(path) {
60 Err(e) => Err(FileError::from_std(e, path)),
61 Ok(mut f) => match f.seek(SeekFrom::Start(from)) {
62 Err(e) => Err(FileError::from_std(e, path)),
63 Ok(_) => {
64 let mut handle = f.take(to - from);
65 let mut buffer = Vec::with_capacity((to - from) as usize);
66
67 if let Err(e) = handle.read_to_end(&mut buffer) {
68 return Err(FileError::from_std(e, path));
69 }
70
71 Ok(buffer)
72 },
73 },
74 }
75}
76
77pub fn read_bytes(path: &str) -> Result<Vec<u8>, FileError> {
78 fs::read(path).map_err(|e| FileError::from_std(e, path))
79}
80
81pub fn read_string(path: &str) -> Result<String, FileError> {
82 let mut s = String::new();
83
84 match File::open(path) {
85 Err(e) => Err(FileError::from_std(e, path)),
86 Ok(mut f) => match f.read_to_string(&mut s) {
87 Ok(_) => Ok(s),
88 Err(e) => Err(FileError::from_std(e, path)),
89 }
90 }
91}
92
93pub fn write_bytes(path: &str, bytes: &[u8], write_mode: WriteMode) -> Result<(), FileError> {
94 let option: OpenOptions = write_mode.into();
95
96 if let WriteMode::Atomic = write_mode {
97 let tmp_path = format!("{path}_tmp__{:x}", rand::random::<u64>());
100
101 match option.open(&tmp_path) {
102 Ok(mut f) => match f.write_all(bytes) {
103 Ok(_) => match rename(&tmp_path, path) {
104 Ok(_) => Ok(()),
105 Err(e) => {
106 remove_file(&tmp_path)?;
107 Err(e)
108 },
109 },
110 Err(e) => {
111 remove_file(&tmp_path)?;
112 Err(FileError::from_std(e, path))
113 },
114 },
115 Err(e) => Err(FileError::from_std(e, path)),
116 }
117 } else {
118 match option.open(path) {
119 Ok(mut f) => match f.write_all(bytes) {
120 Ok(_) => Ok(()),
121 Err(e) => Err(FileError::from_std(e, path)),
122 },
123 Err(e) => Err(FileError::from_std(e, path)),
124 }
125 }
126}
127
128pub fn write_string(path: &str, s: &str, write_mode: WriteMode) -> Result<(), FileError> {
129 write_bytes(path, s.as_bytes(), write_mode)
130}
131
132pub fn file_name(path: &str) -> Result<String, FileError> {
134 let path_buf = PathBuf::from_str(path).unwrap(); match path_buf.file_stem() {
137 None => Ok(String::new()),
138 Some(s) => match s.to_str() {
139 Some(ext) => Ok(ext.to_string()),
140 None => Err(FileError::os_str_err(s.to_os_string())),
141 }
142 }
143}
144
145pub fn extension(path: &str) -> Result<Option<String>, FileError> {
147 let path_buf = PathBuf::from_str(path).unwrap(); match path_buf.extension() {
150 None => Ok(None),
151 Some(s) => match s.to_str() {
152 Some(ext) => Ok(Some(ext.to_string())),
153 None => Err(FileError::os_str_err(s.to_os_string())),
154 }
155 }
156}
157
158pub fn basename(path: &str) -> Result<String, FileError> {
160 let path_buf = PathBuf::from_str(path).unwrap(); match path_buf.file_name() {
163 None => Ok(String::new()), Some(s) => match s.to_str() {
165 Some(ext) => Ok(ext.to_string()),
166 None => Err(FileError::os_str_err(s.to_os_string())),
167 }
168 }
169}
170
171pub fn join(path: &str, child: &str) -> Result<String, FileError> {
173 let mut path_buf = PathBuf::from_str(path).unwrap(); let child = PathBuf::from_str(child).unwrap(); path_buf.push(child);
177
178 match path_buf.to_str() {
179 Some(result) => Ok(result.to_string()),
180 None => Err(FileError::os_str_err(path_buf.into_os_string())),
181 }
182}
183
184pub fn temp_dir() -> Result<String, FileError> {
185 let temp_dir = std::env::temp_dir();
186
187 match temp_dir.to_str() {
188 Some(result) => Ok(result.to_string()),
189 None => Err(FileError::os_str_err(temp_dir.into_os_string())),
190 }
191}
192
193#[inline]
195pub fn join2(path: &str, child: &str) -> Result<String, FileError> {
196 join(path, child)
197}
198
199pub fn join3(path1: &str, path2: &str, path3: &str) -> Result<String, FileError> {
200 join(
201 path1,
202 &join(path2, path3)?,
203 )
204}
205
206pub fn join4(path1: &str, path2: &str, path3: &str, path4: &str) -> Result<String, FileError> {
207 join(
208 &join(path1, path2)?,
209 &join(path3, path4)?,
210 )
211}
212
213pub fn join5(path1: &str, path2: &str, path3: &str, path4: &str, path5: &str) -> Result<String, FileError> {
214 join(
215 &join(path1, path2)?,
216 &join(path3, &join(path4, path5)?)?,
217 )
218}
219
220pub fn set_extension(path: &str, ext: &str) -> Result<String, FileError> {
222 let mut path_buf = PathBuf::from_str(path).unwrap(); if path_buf.set_extension(ext) {
225 match path_buf.to_str() {
226 Some(result) => Ok(result.to_string()),
227 None => Err(FileError::os_str_err(path_buf.into_os_string())),
228 }
229 } else {
230 Ok(path.to_string())
232 }
233}
234
235pub fn is_dir(path: &str) -> bool {
237 PathBuf::from_str(path).map(|path| path.is_dir()).unwrap_or(false)
238}
239
240pub fn is_symlink(path: &str) -> bool {
242 PathBuf::from_str(path).map(|path| path.is_symlink()).unwrap_or(false)
243}
244
245pub fn exists(path: &str) -> bool {
246 PathBuf::from_str(path).map(|path| path.exists()).unwrap_or(false)
247}
248
249pub fn parent(path: &str) -> Result<String, FileError> {
251 let std_path = Path::new(path);
252
253 std_path.parent().map(
254 |p| p.to_string_lossy().to_string()
255 ).ok_or_else(
256 || FileError::unknown(
257 String::from("function `parent` died"),
258 Some(path.to_string()),
259 )
260 )
261}
262
263pub fn try_create_dir(path: &str) -> Result<(), FileError> {
265 match fs::create_dir(path) {
266 Ok(()) => Ok(()),
267 Err(e) => match e.kind() {
268 io::ErrorKind::AlreadyExists => Ok(()),
269 _ => Err(FileError::from_std(e, path)),
270 },
271 }
272}
273
274pub fn create_dir(path: &str) -> Result<(), FileError> {
275 fs::create_dir(path).map_err(|e| FileError::from_std(e, path))
276}
277
278pub fn create_dir_all(path: &str) -> Result<(), FileError> {
279 fs::create_dir_all(path).map_err(|e| FileError::from_std(e, path))
280}
281
282pub fn rename(from: &str, to: &str) -> Result<(), FileError> {
283 fs::rename(from, to).map_err(|e| FileError::from_std(e, from))
284}
285
286pub fn copy_dir(src: &str, dst: &str) -> Result<(), FileError> {
287 create_dir_all(dst)?;
288
289 for e in read_dir(src, false)? {
291 let new_dst = join(dst, &basename(&e)?)?;
292
293 if is_dir(&e) {
294 create_dir_all(&new_dst)?;
295 copy_dir(&e, &new_dst)?;
296 }
297
298 else {
299 copy_file(&e, &new_dst)?;
300 }
301 }
302
303 Ok(())
304}
305
306pub fn copy_file(src: &str, dst: &str) -> Result<u64, FileError> {
308 std::fs::copy(src, dst).map_err(|e| FileError::from_std(e, src)) }
310
311pub fn last_modified(path: &str) -> Result<u64, FileError> {
313 match fs::metadata(path) {
314 Ok(m) => match m.modified() {
315 Ok(m) => {
316 let mut hasher = hash_map::DefaultHasher::new();
317 m.hash(&mut hasher);
318 let hash = hasher.finish();
319
320 Ok(hash)
321 },
322 Err(e) => Err(FileError::from_std(e, path)),
323 },
324 Err(e) => Err(FileError::from_std(e, path)),
325 }
326}
327
328pub fn file_size(path: &str) -> Result<u64, FileError> {
329 match fs::metadata(path) {
330 Ok(m) => Ok(m.len()),
331 Err(e) => Err(FileError::from_std(e, path)),
332 }
333}
334
335pub fn read_dir(path: &str, sort: bool) -> Result<Vec<String>, FileError> {
336 match fs::read_dir(path) {
337 Err(e) => Err(FileError::from_std(e, path)),
338 Ok(entries) => {
339 let mut result = vec![];
340
341 for entry in entries {
342 match entry {
343 Err(e) => {
344 return Err(FileError::from_std(e, path));
345 },
346 Ok(e) => {
347 if let Some(ee) = e.path().to_str() {
348 result.push(ee.to_string());
349 }
350 },
351 }
352 }
353
354 if sort {
355 result.sort();
356 }
357
358 Ok(result)
359 }
360 }
361}
362
363pub fn remove_file(path: &str) -> Result<(), FileError> {
364 fs::remove_file(path).map_err(|e| FileError::from_std(e, path))
365}
366
367pub fn remove_dir(path: &str) -> Result<(), FileError> {
368 fs::remove_dir(path).map_err(|e| FileError::from_std(e, path))
369}
370
371pub fn remove_dir_all(path: &str) -> Result<(), FileError> {
372 fs::remove_dir_all(path).map_err(|e| FileError::from_std(e, path))
373}
374
375pub fn into_abs_path(path: &str) -> Result<String, FileError> {
376 let std_path = Path::new(path);
377
378 if std_path.is_absolute() {
379 Ok(path.to_string())
380 }
381
382 else {
383 Ok(join(
384 ¤t_dir()?,
385 path,
386 )?)
387 }
388}
389
390pub fn current_dir() -> Result<String, FileError> {
391 let cwd = std::env::current_dir().map_err(|e| FileError::from_std(e, "."))?;
392
393 match cwd.to_str() {
394 Some(cwd) => Ok(cwd.to_string()),
395 None => Err(FileError::os_str_err(cwd.into_os_string())),
396 }
397}
398
399pub fn set_current_dir(path: &str) -> Result<(), FileError> {
400 std::env::set_current_dir(path).map_err(|e| FileError::from_std(e, path))
401}
402
403pub fn diff(path: &str, base: &str) -> Result<String, FileError> {
404 match pathdiff::diff_paths(path, base) {
405 Some(path) => match path.to_str() {
406 Some(path) => Ok(path.to_string()),
407 None => Err(FileError::os_str_err(path.into_os_string())),
408 },
409 None => Err(FileError::cannot_diff_path(path.to_string(), base.to_string())),
410 }
411}
412
413pub fn get_relative_path(base: &str, path: &str) -> Result<String, FileError> {
415 Ok(normalize(&diff(
417 &normalize(
419 &into_abs_path(path)?,
420 )?,
421 &normalize(
422 &into_abs_path(base)?,
423 )?,
424 )?)?)
425}
426
427pub fn normalize(path: &str) -> Result<String, FileError> {
428 let mut result = vec![];
429 let path = path.replace("\\", "/");
430
431 for component in path.split("/") {
432 match component {
433 c if c == "." => {},
434 c if c == ".." => if result.is_empty() {
435 result.push(c.to_string());
436 } else {
437 result.pop();
438 },
439 c => { result.push(c.to_string()); },
440 }
441 }
442
443 Ok(result.join("/"))
444}
445
446#[derive(Clone, PartialEq)]
447pub struct FileError {
448 pub kind: FileErrorKind,
449 pub given_path: Option<String>,
450}
451
452impl FileError {
453 pub fn from_std(e: io::Error, given_path: &str) -> Self {
454 let kind = match e.kind() {
455 io::ErrorKind::NotFound => FileErrorKind::FileNotFound,
456 io::ErrorKind::PermissionDenied => FileErrorKind::PermissionDenied,
457 io::ErrorKind::AlreadyExists => FileErrorKind::AlreadyExists,
458 e => FileErrorKind::Unknown(format!("unknown error: {e:?}")),
459 };
460
461 FileError {
462 kind,
463 given_path: Some(given_path.to_string()),
464 }
465 }
466
467 pub(crate) fn os_str_err(os_str: OsString) -> Self {
468 FileError {
469 kind: FileErrorKind::OsStrErr(os_str),
470 given_path: None,
471 }
472 }
473
474 pub(crate) fn cannot_diff_path(path: String, base: String) -> Self {
475 FileError {
476 kind: FileErrorKind::CannotDiffPath(path.to_string(), base),
477 given_path: Some(path),
478 }
479 }
480
481 pub fn unknown(msg: String, path: Option<String>) -> Self {
482 FileError {
483 kind: FileErrorKind::Unknown(msg),
484 given_path: path,
485 }
486 }
487
488 pub fn render_error(&self) -> String {
489 let path = self.given_path.as_ref().map(|p| p.to_string()).unwrap_or(String::new());
490
491 match &self.kind {
492 FileErrorKind::FileNotFound => format!(
493 "file not found: `{path}`"
494 ),
495 FileErrorKind::PermissionDenied => format!(
496 "permission denied: `{path}`"
497 ),
498 FileErrorKind::AlreadyExists => format!(
499 "file already exists: `{path}`"
500 ),
501 FileErrorKind::CannotDiffPath(path, base) => format!(
502 "cannot calc diff: `{path}` and `{base}`"
503 ),
504 FileErrorKind::Unknown(msg) => format!(
505 "unknown file error: `{msg}`"
506 ),
507 FileErrorKind::OsStrErr(os_str) => format!(
508 "error converting os_str: `{os_str:?}`"
509 ),
510 }
511 }
512}
513
514impl fmt::Debug for FileError {
515 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
516 write!(fmt, "{}", self.render_error())
517 }
518}
519
520impl fmt::Display for FileError {
521 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
522 write!(fmt, "{}", self.render_error())
523 }
524}
525
526#[derive(Clone, Debug, PartialEq)]
527pub enum FileErrorKind {
528 FileNotFound,
529 PermissionDenied,
530 AlreadyExists,
531 CannotDiffPath(String, String),
532 Unknown(String),
533 OsStrErr(OsString),
534}