1use crate::error::{BackupFailure, DirCreationFailure, Error, InvalidFilePath};
2use chrono::{DateTime, Datelike, Local, Timelike};
3use pipe_trait::Pipe;
4use std::{
5 fs::{copy, create_dir_all},
6 path::{Path, PathBuf},
7};
8use typed_builder::TypedBuilder;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, TypedBuilder)]
12pub struct Backup<'a> {
13 pub source_file_path: &'a Path,
15 pub source_file_hash: &'a str,
17 #[builder(default = Local::now())]
19 pub date_time: DateTime<Local>,
20}
21
22impl<'a> Backup<'a> {
23 pub fn path(self) -> Result<PathBuf, InvalidFilePath> {
25 let Backup {
26 source_file_path,
27 source_file_hash,
28 date_time,
29 } = self;
30 let source_file_parent = source_file_path.parent().ok_or(InvalidFilePath)?;
31 let source_file_name = source_file_path.file_name().ok_or(InvalidFilePath)?;
32 let date = date_time.date();
33 let date = format!("{:04}-{:02}-{:02}", date.year(), date.month(), date.day());
34 let time = date_time.time();
35 let time = format!(
36 "{:02}.{:02}.{:02}",
37 time.hour(),
38 time.minute(),
39 time.second(),
40 );
41 source_file_parent
42 .join(".id3-backups")
43 .join(source_file_name)
44 .join(date)
45 .join(time)
46 .join(source_file_hash)
47 .pipe(Ok)
48 }
49
50 pub fn backup(self) -> Result<bool, Error> {
56 let src = self.source_file_path;
57 let dest = self.path()?;
58 if dest.exists() {
59 eprintln!("backup: {dest:?} already exists. Skip.");
60 return Ok(false);
61 }
62 if let Some(parent) = dest.parent() {
63 eprintln!("backup: Creating a directory at {parent:?}");
64 create_dir_all(parent).map_err(move |error| DirCreationFailure {
65 dir: parent.to_path_buf(),
66 error,
67 })?;
68 }
69 eprintln!("backup: Copying {src:?} to {dest:?}");
70 copy(src, &dest).map_err(|error| BackupFailure {
71 src: src.to_path_buf(),
72 dest: dest.to_path_buf(),
73 error,
74 })?;
75 Ok(true)
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::Backup;
82 use chrono::{Local, TimeZone};
83 use pretty_assertions::assert_eq;
84 use std::path::Path;
85
86 #[test]
87 fn file_path() {
88 let source_file_parent = Path::new("Music").join("fav");
89 let source_file_name = "mysterious-file.mp3";
90 let source_file_path = source_file_parent.join(source_file_name);
91 let received = Backup::builder()
92 .source_file_path(&source_file_path)
93 .source_file_hash("34a1e24aba0a02316b786933761beedcea40c8eda46a39054f994e0fdef87adf")
94 .date_time(Local.ymd(2022, 7, 16).and_hms(12, 26, 5))
95 .build()
96 .path()
97 .expect("get backup file path");
98 let expected = source_file_parent
99 .join(".id3-backups")
100 .join(source_file_name)
101 .join("2022-07-16")
102 .join("12.26.05")
103 .join("34a1e24aba0a02316b786933761beedcea40c8eda46a39054f994e0fdef87adf");
104 assert_eq!(received, expected);
105 }
106}