datman 0.1.0

A chunked and deduplicated backup system using Yama
Documentation
/*
This file is part of Yama.

Yama is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Yama is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Yama.  If not, see <https://www.gnu.org/licenses/>.
*/


use crate::commands::backup::POINTER_DATETIME_FORMAT;
use crate::descriptor::load_descriptor;
use anyhow::bail;
use chrono::{DateTime, NaiveDateTime, Utc};
use itertools::Itertools;
use log::{info, warn};
use std::path::Path;
use yama::commands::{load_pile_descriptor, open_pile};
use yama::pile::{Pile, RawPile};

pub type PileT = Pile<Box<dyn RawPile>>;

pub fn extract(
    destination: &Path,
    descriptor_path: &Path,
    source_name: Option<&str>,
    pile_name: &str,
    before: Option<DateTime<Utc>>,
    after: Option<DateTime<Utc>>,
    apply_permissions: bool,
    apply_mtime: bool,
    apply_ownership: bool,
    num_workers: u8,
) -> anyhow::Result<()> {
    if destination.exists() {
        bail!("For now, the destination is not allowed to exist prior to extraction.");
    }

    let descriptor = load_descriptor(descriptor_path)?;
    let dest_descriptor = &descriptor.piles[pile_name];
    let dest_pile_path = descriptor_path.join(&dest_descriptor.path);
    let pile_descriptor = load_pile_descriptor(&dest_pile_path)?;
    let pile = open_pile(&dest_pile_path, &pile_descriptor)?;

    std::fs::create_dir_all(&destination)?;

    let mut pointers_to_extract = Vec::new();

    match source_name {
        Some(source_name) => match find_pointer_for_source(source_name, &pile, &before, &after)? {
            None => {
                bail!(
                    "No pointer found for {:?} and it's the only one requested.",
                    source_name
                );
            }
            Some(pointer) => {
                pointers_to_extract.push(pointer);
            }
        },
        None => {
            for source in descriptor.source.keys() {
                match find_pointer_for_source(source, &pile, &before, &after)? {
                    None => {
                        warn!("No pointer found for {:?}! Carrying on anyway...", source);
                    }
                    Some(pointer) => {
                        pointers_to_extract.push(pointer);
                    }
                }
            }
        }
    }

    extract_pointers_into_already_created_directory(
        destination,
        pointers_to_extract,
        &pile,
        apply_permissions,
        apply_mtime,
        apply_ownership,
        num_workers,
    )?;

    Ok(())
}

fn find_pointer_for_source(
    source_name: &str,
    pile: &PileT,
    before: &Option<DateTime<Utc>>,
    after: &Option<DateTime<Utc>>,
) -> anyhow::Result<Option<String>> {
    let mut current_choice: Option<(String, DateTime<Utc>)> = None;
    for pointer_name in pile.list_pointers()? {
        if let Some((pointer_source_name, encoded_datetime)) =
            pointer_name.split('+').collect_tuple()
        {
            if source_name != pointer_source_name {
                // don't accept pointers for other sources!
                continue;
            }
            match NaiveDateTime::parse_from_str(encoded_datetime, POINTER_DATETIME_FORMAT) {
                Ok(decoded_datetime) => {
                    let datetime = DateTime::from_utc(decoded_datetime, Utc);

                    if let Some(before) = before {
                        if before < &datetime {
                            // datetime is after the 'before' time
                            continue;
                        }
                    } else if let Some(after) = after {
                        if &datetime < after {
                            // datetime is before the 'after' time
                            continue;
                        }
                    }

                    match current_choice.as_ref() {
                        None => current_choice = Some((pointer_name, datetime)),
                        Some((_current_name, current_datetime)) => {
                            let should_replace = if after.is_some() {
                                // if we want the first one after a time, we want the earliest option!
                                // so replace if new datetime is earlier than current
                                &datetime < current_datetime
                            } else {
                                // replace if new datetime is after current datetime
                                current_datetime < &datetime
                            };
                            if should_replace {
                                current_choice = Some((pointer_name, datetime));
                            }
                        }
                    }
                }
                Err(e) => {
                    warn!(
                        "Ignoring {:?} because it seems to have a bad datetime: {:?}",
                        pointer_name, e
                    );
                }
            }
        }
    }
    Ok(current_choice.map(|(a, _)| a))
}

fn extract_pointers_into_already_created_directory(
    target: &Path,
    pointers: Vec<String>,
    pile: &PileT,
    apply_permissions: bool,
    apply_mtime: bool,
    apply_ownership: bool,
    num_workers: u8,
) -> anyhow::Result<()> {
    for pointer in pointers {
        info!("Extracting {:?} now.", pointer);

        let pointer_target_dir = &target.join(&pointer);
        std::fs::create_dir(pointer_target_dir)?;

        yama::operations::extracting::extract_from_pointer_name(
            pointer_target_dir,
            &pointer,
            pile,
            true,
            num_workers,
            apply_permissions,
            apply_mtime,
            apply_ownership,
        )?;
    }
    Ok(())
}