pijul 0.7.2

A patch-based distributed version control system, easy to use and fast. Command-line interface.
use toml;
use libpijul::fs_representation::meta_file;
use libpijul::KeyPair;
use error::{ErrorKind, Result};
use std::fs::{File, create_dir_all};
use std::io::{Read, Write};
use std::collections::BTreeMap;
use commands::remote::{Remote, parse_remote};
use std::path::{Path, PathBuf};
use std;

pub const DEFAULT_REMOTE: &'static str = "remote";

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Repository {
    pub address: String,
    pub port: Option<u16>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Meta {
    pub authors: Vec<String>,
    pub signing_key: Option<String>,
    pub pull: Option<String>,
    pub push: Option<String>,
    pub remote: BTreeMap<String, Repository>
}

impl Meta {
    pub fn load(r: &Path) -> Result<Meta> {
        let mut str = String::new();
        {
            let mut f = try!(File::open(meta_file(r)));
            try!(f.read_to_string(&mut str));
        }
        Ok(toml::from_str(&str)?)
    }
    pub fn new() -> Meta {
        Meta {
            authors: Vec::new(),
            signing_key: None,
            pull: None,
            push: None,
            remote: BTreeMap::new(),
        }
    }
    pub fn save(&self, r: &Path) -> Result<()> {
        let mut f = try!(File::create(meta_file(r)));
        let s: String = toml::to_string(&self)?;
        try!(f.write_all(s.as_bytes()));
        Ok(())
    }


    fn parse_remote<'a>(&'a self, remote: &'a str, port: Option<u16>, base_path: Option<&'a Path>) -> Remote<'a> {
        if let Some(repo) = self.remote.get(remote) {
            parse_remote(&repo.address, port.or(repo.port), None, base_path)
        } else {
            parse_remote(remote, port, None, base_path)
        }
    }

    fn get_remote<'a>(&'a self, remote: Option<&'a str>, default_remote: Option<&'a String>,
                      port: Option<u16>,
                      base_path: Option<&'a Path>) -> Result<Remote<'a>> {
        if let Some(remote) = remote {
            Ok(self.parse_remote(remote, port, base_path))
        } else if let Some(ref remote) = default_remote {
            Ok(self.parse_remote(remote, port, base_path))
        } else if self.remote.len() == 1 {
            let remote = self.remote.keys().next().unwrap();
            Ok(self.parse_remote(remote, port, base_path))
        } else {
            Err(ErrorKind::MissingRemoteRepository.into())
        }
    }


    pub fn pull<'a>(&'a self, remote: Option<&'a str>, port: Option<u16>,
                    base_path: Option<&'a Path>) -> Result<Remote<'a>> {
        self.get_remote(remote, self.pull.as_ref(), port, base_path)
    }

    pub fn push<'a>(&'a self, remote: Option<&'a str>, port: Option<u16>, base_path: Option<&'a Path>) -> Result<Remote<'a>> {
        self.get_remote(remote, self.push.as_ref(), port, base_path)
    }

    pub fn signing_key(&self) -> Result<Option<KeyPair>> {
        if let Some(ref path) = self.signing_key {
            let mut f = File::open(path)?;
            let mut key = [0; 85];
            f.read_exact(&mut key)?;
            Ok(Some(KeyPair::from_pkcs8(&key)?))
        } else {
            Ok(None)
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Global {
    pub author: String,
    pub signing_key: Option<String>,
}

fn global_path() -> PathBuf {
    let mut path: PathBuf = std::env::home_dir().unwrap();
    path.push(".pijulconfig");
    path
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyType {
    SSH,
    Signing
}

pub fn generate_key<P:AsRef<Path>>(dot_pijul: P, keytype: KeyType) -> Result<()> {
    use ring::signature::Ed25519KeyPair;
    use ring::rand::SystemRandom;
    let key = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new())?;
    // let key = encode_config(&key[..], URL_SAFE_NO_PAD);
    create_dir_all(dot_pijul.as_ref())?;
    let f = dot_pijul.as_ref().join(match keytype {
        KeyType::SSH => "id_ed25519.pkcs8",
        KeyType::Signing => "sig_ed25519.pkcs8",
    });
    debug!("generate_key: {:?}", f);
    if std::fs::metadata(&f).is_err() {
        let mut f = File::create(&f)?;
        f.write_all(&key)?;
        Ok(())
    } else {
        Err(ErrorKind::WillNotOverwriteKeyFile(f).into())
    }
}

pub fn load_key<P:AsRef<Path>>(dot_pijul: P, keytype: KeyType) -> Result<KeyPair> {
    let f = dot_pijul.as_ref().join(match keytype {
        KeyType::SSH => "id_ed25519.pkcs8",
        KeyType::Signing => "sig_ed25519.pkcs8",
    });
    debug!("load_key: {:?}", f);
    let mut f = File::open(&f)?;
    let mut key = Vec::new();
    f.read_to_end(&mut key)?;
    // let key = decode_config(&s, URL_SAFE_NO_PAD)?;
    Ok(KeyPair::from_pkcs8(&key)?)
}

pub fn generate_global_key(keytype: KeyType) -> Result<()> {
    generate_key(&global_path(), keytype)
}

pub fn load_global_key(keytype: KeyType) -> Result<KeyPair> {
    load_key(&global_path(), keytype)
}

pub fn load_global_or_local_key<P:AsRef<Path>>(dot_pijul: Option<P>, keytype: KeyType) -> Result<KeyPair> {
    if let Some(dot_pijul) = dot_pijul {
        if let Ok(key) = load_key(dot_pijul.as_ref(), keytype) {
            return Ok(key)
        }
    }
    load_global_key(keytype)
}

impl Global {

    pub fn new() -> Self {
        Global {
            author: String::new(),
            signing_key: None
        }
    }


    pub fn load() -> Result<Self> {

        let mut path = global_path();
        path.push("config");
        let mut str = String::new();
        {
            let mut f = try!(File::open(&path));
            try!(f.read_to_string(&mut str));
        }
        Ok(toml::from_str(&str)?)
    }

    pub fn save(&self) -> Result<()> {
        let mut path = global_path();
        create_dir_all(&path)?;
        path.push("config");
        let mut f = try!(File::create(&path));
        let s: String = toml::to_string(&self)?;
        try!(f.write_all(s.as_bytes()));
        Ok(())
    }

}