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 std::path::Path;

use anyhow::{anyhow, bail};

use crate::descriptor::{load_descriptor, SourceDescriptor};
use crate::labelling::{
    label_node, load_labelling_rules, save_labelling_rules, GlobRule, Label, State,
};
use crate::tree::{scan, FileTree, FileTree1};
use arc_interner::ArcIntern;
use humansize::FileSize;
use std::io::{stdin, stdout, Write};

pub fn calculate_sizes(node: &mut FileTree1<u64>, real_path: &Path) -> anyhow::Result<u64> {
    match node {
        FileTree::NormalFile { meta, .. } => {
            let size = std::fs::metadata(real_path)?.len();
            *meta = size;
            Ok(size)
        }
        FileTree::Directory { children, meta, .. } => {
            let mut size = 0;
            for (name, child) in children.iter_mut() {
                size += calculate_sizes(child, &real_path.join(name))?;
            }
            *meta = size;
            Ok(size)
        }
        FileTree::SymbolicLink { meta, target, .. } => {
            *meta = target.len() as u64;
            Ok(target.len() as u64)
        }
        FileTree::Other(_) => Ok(0),
    }
}

pub fn string_to_outcome(s: &str) -> State {
    match s {
        "s" => State::Split,
        "x" => State::Excluded,
        other => State::Labelled(Label(ArcIntern::new(other.to_owned()))),
    }
}

pub fn session(path: &Path, source_name: String) -> anyhow::Result<()> {
    let mut current_path = String::from("");

    let descriptor = load_descriptor(path)?;
    let source_descriptor = descriptor
        .source
        .get(&source_name)
        .ok_or_else(|| anyhow!("Could not find source {:?}!", source_name))?;

    let directory = match source_descriptor {
        SourceDescriptor::DirectorySource { directory, .. } => directory,
        SourceDescriptor::VirtualSource { .. } => {
            bail!("Cannot browse virtual source.");
        }
    };

    println!("Scanning source; this might take a little while...");
    let mut dir_scan: FileTree1<Option<State>> = scan(directory)?
        .ok_or_else(|| anyhow!("Empty source."))?
        .replace_meta(&None);

    let mut size_dir_scan: FileTree1<u64> = dir_scan.replace_meta(&0);
    calculate_sizes(&mut size_dir_scan, directory)?;
    let mut rules = load_labelling_rules(path, &source_name)?;
    let labels = descriptor
        .labels
        .iter()
        .map(|l| Label(ArcIntern::new(l.clone())))
        .collect();
    label_node("".to_owned(), None, &mut dir_scan, &labels, &rules)?;

    loop {
        println!("---------------------------------------------------------");
        println!("| {}", current_path);
        println!("----");
        if let Some(dir_node) = dir_scan.get_by_path(&current_path) {
            if let FileTree::Directory { children, .. } = dir_node {
                let size_node = size_dir_scan.get_by_path(&current_path).unwrap();
                for (idx, (child_name, child)) in children.iter().enumerate() {
                    let size_child = size_node
                        .get_by_path(child_name)
                        .unwrap()
                        .get_metadata()
                        .unwrap();
                    if child.is_dir() {
                        println!("{}/", child_name);
                    } else if child.is_symlink() {
                        println!("{} (symlink)", child_name);
                    } else {
                        println!("{}", child_name);
                    }

                    print!("\t[{:3}] ", idx);
                    match child.get_metadata().unwrap() {
                        None => {
                            print!("unlabelled ");
                        }
                        Some(state) => match state {
                            State::Labelled(label) => {
                                print!("l:{} ", label.0.as_ref());
                            }
                            State::Split => {
                                print!("split ");
                            }
                            State::Excluded => {
                                print!("excluded ");
                            }
                        },
                    }

                    println!(
                        "({})",
                        size_child
                            .file_size(humansize::file_size_opts::BINARY)
                            .unwrap()
                    );
                }

                print!("\n> ");
                stdout().flush()?;

                let mut next_command = String::new();
                if stdin().read_line(&mut next_command)? > 0 {
                    let split: Vec<&str> = next_command.trim_end_matches('\n').split(' ').collect();

                    match split[0] {
                        "x" => {
                            if let Ok(id) = split[1].parse::<usize>() {
                                let entry = children
                                    .iter()
                                    .enumerate()
                                    .find(|(index, _item)| *index == id);
                                if let Some((_index, (name, _entry))) = entry {
                                    let entry_path = format!("{}/{}", &current_path, name);
                                    rules
                                        .position_based_rules
                                        .insert(entry_path, State::Excluded);
                                } else {
                                    eprintln!("not found.");
                                }
                            } else {
                                eprintln!("bad int :(");
                            }
                        }
                        "s" => {
                            if let Ok(id) = split[1].parse::<usize>() {
                                let entry = children
                                    .iter()
                                    .enumerate()
                                    .find(|(index, _item)| *index == id);
                                if let Some((_index, (name, _entry))) = entry {
                                    let entry_path = format!("{}/{}", &current_path, name);
                                    rules.position_based_rules.insert(entry_path, State::Split);
                                } else {
                                    eprintln!("not found.");
                                }
                            } else {
                                eprintln!("bad int :(");
                            }
                        }
                        "p" => {
                            let outcome = split[1];
                            let pattern = split[2];
                            match glob::Pattern::new(&pattern) {
                                Ok(glob) => {
                                    rules.glob_based_rules.push(GlobRule {
                                        pattern: pattern.to_owned(),
                                        glob,
                                        outcome: string_to_outcome(&outcome),
                                    });
                                }
                                Err(e) => {
                                    eprintln!("{:?}", e);
                                }
                            }
                        }
                        "q" => {
                            break;
                        }
                        other => {
                            if other.chars().all(char::is_numeric) {
                                let id: usize = other.parse().unwrap();
                                let entry = children
                                    .iter()
                                    .enumerate()
                                    .find(|(index, _item)| *index == id);
                                if let Some((_index, (name, entry))) = entry {
                                    if entry.is_dir() {
                                        current_path.extend("/".chars());
                                        current_path.extend(name.chars());
                                    } else {
                                        eprintln!("not a dir.");
                                    }
                                }
                            } else {
                                let label = split[1];
                                let id: usize = split[2].parse().unwrap(); // TODO
                                let entry = children
                                    .iter()
                                    .enumerate()
                                    .find(|(index, _item)| *index == id);
                                if let Some((_index, (name, _entry))) = entry {
                                    let entry_path = format!("{}/{}", &current_path, name);
                                    rules.position_based_rules.insert(
                                        entry_path,
                                        State::Labelled(Label(ArcIntern::new(label.to_owned()))),
                                    );
                                }
                            }
                        }
                    }
                } else {
                    println!("ending.");
                    break;
                }
            } else {
                break;
            }
        } else {
            break;
        }
    }

    save_labelling_rules(path, &source_name, &rules)?;

    Ok(())
}