use std::ffi::OsString;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use anyhow::{bail, Result};
use byteorder::{BigEndian, ByteOrder};
use super::{Blob, FileMeta, Hashable, Object, Tree, Diff, DIffTag};
use crate::nss_io::file_system;
use crate::repo::error::*;
use crate::repo::repository::NssRepository;
#[derive(Debug, Clone, PartialEq)]
pub struct Index {
pub version: u32,
pub filemetas: Vec<FileMeta>,
}
impl Index {
pub fn empty() -> Self {
Self {
version: 1,
filemetas: vec![],
}
}
pub fn new_all(repository: &NssRepository) -> Result<Self> {
let mut all_paths = repository.get_all_paths_ignore(repository.path());
all_paths.sort();
let filemetas = all_paths
.iter()
.map(|path| FileMeta::new(repository, path).unwrap())
.collect::<Vec<_>>();
Ok(Self {
version: 1,
filemetas,
})
}
pub fn add<P: AsRef<Path>>(
&mut self,
repository: &NssRepository,
file_path: P,
temp_prefix: Option<P>,
) -> Result<()> {
let add_filemeta = match temp_prefix {
Some(p) => FileMeta::new_temp(file_path, p)?,
None => FileMeta::new(repository, file_path)?,
};
let mut new_filemetas: Vec<FileMeta> = vec![];
for filemeta in self.filemetas.clone() {
if filemeta == add_filemeta {
continue;
} else {
new_filemetas.push(filemeta);
}
}
new_filemetas.push(add_filemeta);
new_filemetas.sort_by(|a, b| b.filename.cmp(&a.filename));
self.filemetas = new_filemetas;
Ok(())
}
pub fn try_from_tree(repository: &NssRepository, tree: Tree) -> Result<Self> {
let mut index = Index::empty();
let mut path_blob: HashMap<PathBuf, Blob> = HashMap::new();
let temp_dir = repository.temp_path(hex::encode(tree.to_hash()));
file_system::create_dir(&temp_dir)?;
push_paths(repository, &mut path_blob, tree, &temp_dir)?;
for (path, blob) in path_blob {
file_system::create_dir(path.parent().unwrap())?;
file_system::create_file_with_buffer(&path, &blob.content)?;
index.add(repository, path, Some(temp_dir.clone()))?;
}
file_system::remove_dir_all(temp_dir)?;
Ok(index)
}
}
fn padding(size: usize) -> usize {
let floor = (size - 2) / 8;
let target = (floor + 1) * 8 + 2;
target - size
}
fn push_paths(
repository: &NssRepository,
path_blob: &mut HashMap<PathBuf, Blob>,
tree: Tree,
base_path: &Path,
) -> Result<()> {
for entry in tree.entries {
let path = base_path.join(&entry.name);
if entry.as_type() == "blob" {
let blob = match repository.read_object(hex::encode(&entry.hash))? {
Object::Blob(b) => b,
_ => bail!(ObjectError::DontMatchType(
"Blob".to_string(),
hex::encode(entry.hash)
)),
};
path_blob.insert(path, blob);
} else {
let hash = hex::encode(entry.hash);
let sub_tree = match repository.read_object(&hash)? {
Object::Tree(t) => t,
_ => bail!(ObjectError::DontMatchType("Tree".to_string(), hash)),
};
push_paths(repository, path_blob, sub_tree, &path)?
}
}
Ok(())
}
pub trait IndexVesion1 {
fn as_bytes(&self) -> Vec<u8>;
fn from_rawindex(buf: Vec<u8>) -> Result<Self>
where
Self: Sized;
}
impl IndexVesion1 for Index {
fn as_bytes(&self) -> Vec<u8> {
let index_header = b"DIRC";
let index_version = self.version;
let entry_num = self.filemetas.len() as u32;
let header = [
*index_header,
index_version.to_be_bytes(),
entry_num.to_be_bytes(),
]
.concat();
let mut filemetas_vec: Vec<Vec<u8>> = vec![];
for filemeta in &self.filemetas {
let len = 62 + filemeta.filename_size as usize;
let padding = (0..(8 - len % 8)).map(|_| b'\0').collect::<Vec<u8>>();
let filemeta_vec = [filemeta.as_bytes(), padding].concat();
filemetas_vec.push(filemeta_vec)
}
[header, filemetas_vec.concat()].concat()
}
fn from_rawindex(buf: Vec<u8>) -> Result<Self> {
if buf == Vec::<u8>::new() {
bail!("First index");
}
let entry_num = BigEndian::read_u32(&buf[8..12]) as usize;
let mut start_size = 12_usize;
let mut filemetas: Vec<FileMeta> = vec![];
for _ in 0..entry_num {
let name_size =
BigEndian::read_u16(&buf[(start_size + 60)..(start_size + 62)]) as usize;
filemetas.push(FileMeta::from_rawindex(
&buf[(start_size)..(start_size + 62 + name_size)],
));
let padding_size = padding(name_size);
start_size = start_size + 62 + name_size + padding_size;
}
Ok(Self {
version: 1,
filemetas,
})
}
}
impl Diff<Index, OsString> for Index {
fn diff(&self, vs: Index) -> Vec<(DIffTag, OsString)> {
let mut changes = Vec::new();
let new_metas: HashMap<OsString, Vec<u8>> = self.filemetas.iter().map(|f| (f.filename.clone(), f.hash.clone())).collect();
let old_metas: HashMap<OsString, Vec<u8>> = vs.filemetas.iter().map(|f| (f.filename.clone(), f.hash.clone())).collect();
old_metas.iter().for_each(|(k, v)| {
if !new_metas.contains_key(k) {
changes.push((DIffTag::Delete, k.clone()))
} else {
if new_metas.get(k) == Some(v) {
changes.push((DIffTag::Equal, k.clone()))
} else {
changes.push((DIffTag::Replace, k.clone()))
}
}
});
new_metas.iter().for_each(|(k, _v)| {
if !old_metas.contains_key(k) {
changes.push((DIffTag::Insert, k.clone()))
}
});
changes
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::fs;
use std::path::PathBuf;
use testdir::testdir;
#[test]
fn test_index_empty() {
let empty_index = Index::empty();
let test_index = Index {
version: 1,
filemetas: vec![],
};
assert_eq!(empty_index, test_index);
}
#[test]
fn test_index_new_all() {}
#[test]
fn test_index_from_rawindex() {}
#[test]
fn test_index_add() {}
#[test]
fn test_as_bytes() {}
#[test]
fn test_padding() {}
#[test]
fn test_index_try_from_tree() {}
#[test]
fn test_push_paths() {}
#[test]
fn test_to_tree() {}
#[test]
fn test_index_diff() {
let temp_dir = testdir!();
fs::create_dir(temp_dir.join("sub")).unwrap();
println!("Test Directory: {}", temp_dir.display());
let test_file_root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.join("tests")
.join("test_repo")
.join("first.rs");
let test_file_root2 = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.join("tests")
.join("test_repo")
.join("second.rs");
let test_file_root3 = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.join("tests")
.join("test_repo2")
.join("first.rs");
let repository = NssRepository::new(temp_dir.clone());
fs::copy(test_file_root, repository.path().join("first.rs")).unwrap();
fs::copy(test_file_root2, repository.path().join("second.rs")).unwrap();
let repository2 = NssRepository::new(temp_dir.join("sub").clone());
fs::copy(test_file_root3, repository2.path().join("first.rs")).unwrap();
let test_filemeta1 =
FileMeta::new(&repository, repository.path().join("first.rs")).unwrap();
let test_filemeta2 =
FileMeta::new(&repository, repository.path().join("second.rs")).unwrap();
let test_filemeta3 =
FileMeta::new(&repository2, repository2.path().join("first.rs")).unwrap();
let mut index1 = Index::empty();
index1.filemetas.push(test_filemeta1);
let mut index2 = Index::empty();
index2.filemetas.push(test_filemeta2);
index2.filemetas.push(test_filemeta3);
let change = index1.diff(index2);
for c in change {
println!("{:?} {:?}", c.0, c.1);
}
}
}