ajour_core/
backup.rs

1use crate::error::FilesystemError;
2use crate::fs::backup::{Backup, ZipBackup, ZstdBackup};
3use crate::repository::CompressionFormat;
4
5use chrono::{Local, NaiveDateTime};
6use std::convert::TryFrom;
7use std::path::{Path, PathBuf};
8
9/// Creates a .zip archive from the list of source folders and
10/// saves it to the dest folder.
11pub async fn backup_folders(
12    src_folders: Vec<BackupFolder>,
13    mut dest: PathBuf,
14    compression: CompressionFormat,
15) -> Result<NaiveDateTime, FilesystemError> {
16    let now = Local::now();
17
18    dest.push(format!(
19        "ajour_backup_{}.{}",
20        now.format("%Y-%m-%d_%H-%M-%S"),
21        compression.file_ext(),
22    ));
23
24    match compression {
25        CompressionFormat::Zip => ZipBackup::new(src_folders, &dest).backup()?,
26        CompressionFormat::Zstd => ZstdBackup::new(src_folders, &dest).backup()?,
27    }
28
29    // Won't fail since we pass it the correct format
30    let as_of = Archive::try_from(dest).unwrap().as_of;
31
32    Ok(as_of)
33}
34
35/// Finds the latest archive in the supplied backup folder and returns
36/// the datetime it was saved
37pub async fn latest_backup(backup_dir: PathBuf) -> Option<NaiveDateTime> {
38    let zip_pattern = format!("{}/ajour_backup_[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_[0-9][0-9]-[0-9][0-9]-[0-9][0-9].zip", backup_dir.display());
39    let zstd_pattern = format!("{}/ajour_backup_[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_[0-9][0-9]-[0-9][0-9]-[0-9][0-9].tar.zst", backup_dir.display());
40
41    let mut backups = vec![];
42
43    for entry in glob::glob(&zip_pattern)
44        .unwrap()
45        .chain(glob::glob(&zstd_pattern).unwrap())
46    {
47        if let Ok(path) = entry {
48            if let Ok(archive) = Archive::try_from(path) {
49                backups.push(archive.as_of);
50            }
51        }
52    }
53
54    // Apparently NaiveDateTime sorts in Desc order by default, no need to reverse
55    backups.sort();
56    backups.pop()
57}
58
59/// Specifies a folder that we want backed up. `prefix` will get stripped out of
60/// the path of each entry in the archive.
61pub struct BackupFolder {
62    pub path: PathBuf,
63    pub prefix: PathBuf,
64}
65
66impl BackupFolder {
67    pub fn new(path: impl AsRef<Path>, prefix: impl AsRef<Path>) -> BackupFolder {
68        BackupFolder {
69            path: path.as_ref().to_owned(),
70            prefix: prefix.as_ref().to_owned(),
71        }
72    }
73}
74
75/// Metadata for our archive saved on the filesystem. Converted from a `PathBuf` with
76/// the correct naming convention
77struct Archive {
78    pub as_of: NaiveDateTime,
79}
80
81impl TryFrom<PathBuf> for Archive {
82    type Error = chrono::ParseError;
83
84    fn try_from(path: PathBuf) -> Result<Archive, chrono::ParseError> {
85        let mut file_stem = path.file_stem().unwrap().to_str().unwrap();
86
87        // in the case of "file.tar.zst" path.file_stem() will return "file.tar", we still need to
88        // drop the extension
89        if let Some(i) = file_stem.find('.') {
90            file_stem = file_stem.split_at(i).0;
91        }
92
93        let date_str = format!(
94            "{} {}",
95            file_stem.split('_').nth(2).unwrap_or_default(),
96            file_stem.split('_').nth(3).unwrap_or_default()
97        );
98
99        let as_of = NaiveDateTime::parse_from_str(&date_str, "%Y-%m-%d %H-%M-%S")?;
100
101        Ok(Archive { as_of })
102    }
103}