1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Surefile store

use Result;
use SureTree;
use chrono::{DateTime, Utc};
use failure::err_msg;
use std::collections::BTreeMap;
use std::path::Path;

mod plain;
mod bk;
mod weave;

pub use self::plain::Plain;
pub use self::bk::{BkSureFile, BkStore, bk_setup};
pub use self::weave::WeaveStore;

/// Tags are just key/value pairs.  Both key and value should be printable strings.
pub type StoreTags = BTreeMap<String, String>;

/// Something that can store and retrieve SureTrees.
pub trait Store {
    /// Write a new SureTree to the store.  The store may write the tags in the version to help
    /// identify information about what was captured.
    fn write_new(&self, tree: &SureTree, tags: &StoreTags) -> Result<()>;

    /// Attempt to load a sure version, based on the descriptor given.
    fn load(&self, version: Version) -> Result<SureTree>;

    /// Retrieve the available versions, in the store.  These should be listed, newest first.
    fn get_versions(&self) -> Result<Vec<StoreVersion>>;
}

/// Indicator of which version of sure data to load.
#[derive(Clone, Debug)]
pub enum Version {
    Latest,
    Prior,
    Tagged(String),
}

/// Information about a given version in the store.
#[derive(Clone, Debug)]
pub struct StoreVersion {
    /// A descriptive name.  May be the "name" tag given when this version was created.
    pub name: String,
    /// A timestamp of when the version was made.
    pub time: DateTime<Utc>,
    /// The identifier for this version.
    pub version: Version,
}

/// Parse a command line specified path to determine the parameters and type of store desired.  The
/// path can be the path to a directory.  In this case, look at possible filenames to determine the
/// other parameters.  The path can also give a filename of one of the surefiles, and we will
/// derive the name information from that.
pub fn parse_store(text: &str) -> Result<Box<Store>> {
    // First determine if this path is a directory.
    let p = Path::new(text);
    info!("Parsing: {:?}", p);

    // If we're given an existing directory, construct a store directly from it.
    // TODO: Look in the directory to see what might be there.
    if p.is_dir() {
        // Check for BK directory, and reject without explicit name.
        if p.join(".bk").is_dir() {
            return Err(
                err_msg("Store appears to be a Bitkeeper dir, specify full filename"),
            );
        }

        return Ok(Box::new(Plain {
            path: p.to_path_buf(),
            base: "2sure".to_string(),
            compressed: true,
        }));
    }

    // Otherwise, try to get the parent.  If it seems to be empty, use the current directory as the
    // path.
    let dir = match p.parent() {
        None => return Err(err_msg("Unknown directory specified")),
        Some(dir) => {
            if dir.as_os_str().is_empty() {
                Path::new(".")
            } else {
                dir
            }
        }
    };

    if !dir.is_dir() {
        return Err(err_msg("File is not in a directory"));
    }

    let base = match p.file_name() {
        Some(name) => name,
        None => return Err(err_msg("Path does not have a final file component")),
    };
    let base = match base.to_str() {
        Some(name) => name,
        None => panic!("Path came from string, yet is no longer UTF-8"),
    };

    let (base, compressed) = if base.ends_with(".gz") {
        (&base[..base.len() - 3], true)
    } else {
        (base, false)
    };

    // Check for weave format.
    if base.ends_with(".weave") {
        let base = &base[..base.len() - 6];
        return Ok(Box::new(WeaveStore::new(dir, base, compressed)));
    }

    // Strip off known suffixes.
    let base = if base.ends_with(".dat") || base.ends_with(".bak") {
        &base[..base.len() - 4]
    } else {
        base
    };

    // Check for bitkeeper.
    if dir.join(".bk").is_dir() {
        if compressed {
            return Err(
                err_msg("Bitkeeper names should not be compressed, remove .gz suffix"),
            );
        }

        return Ok(Box::new(BkStore::new(dir, base)));
    }

    Ok(Box::new(WeaveStore::new(dir, base, compressed)))
}