diff --git a/Cargo.toml b/Cargo.toml index 14a7e17..5e4d743 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,6 @@ colored = "2.1.0" rand = "0.8.5" color-backtrace = "0.6.1" once_cell = "1.19.0" -backtrace = "0.3.69" \ No newline at end of file +backtrace = "0.3.69" +flate2 = "1.0.28" +base64 = "0.21.5" diff --git a/src/models/blob.rs b/src/models/blob.rs index e9a2acc..3df606a 100644 --- a/src/models/blob.rs +++ b/src/models/blob.rs @@ -1,3 +1,7 @@ +use base64::Engine; +use flate2::{read::GzDecoder, write::GzEncoder, Compression}; +use std::io::{Read, Write}; + use crate::{ models::Hash, utils::{store, util}, @@ -17,28 +21,53 @@ impl Blob { /// 从源文件新建blob对象,并直接保存到/objects/中 pub fn new(file: &Path) -> Blob { let data = fs::read_to_string(file).expect(format!("无法读取文件:{:?}", file).as_str()); - let hash = util::calc_hash(&data); - let blob = Blob { hash, data }; + let mut blob = Blob { hash: "".to_string(), data }; blob.save(); blob } pub fn load(hash: &String) -> Blob { let s = store::Store::new(); - let data = s.load(hash); + let encoded = s.load(hash); + let compressed_data = base64::engine::general_purpose::STANDARD_NO_PAD.decode(&encoded).unwrap(); + let mut decompress_decoder = GzDecoder::new(&compressed_data[..]); + let mut data = String::new(); + decompress_decoder.read_to_string(&mut data).unwrap(); Blob { hash: hash.clone(), data } } - /// 写入文件;优化:文件已存在时不做操作 - pub fn save(&self) { + /// 写入文件 + pub fn save(&mut self) -> Hash { let s = store::Store::new(); - if !s.contains(&self.hash) { - let hash = s.save(&self.data); - assert_eq!(hash, self.hash); - } + let mut cmopress_encoder = GzEncoder::new(Vec::new(), Compression::default()); + cmopress_encoder.write_all(self.data.as_bytes()).unwrap(); + let compressed_data = cmopress_encoder.finish().unwrap(); + let encoded_data = base64::engine::general_purpose::STANDARD_NO_PAD.encode(&compressed_data); + let hash: String = s.save(&encoded_data); + self.hash = hash; + self.hash.clone() } pub fn get_hash(&self) -> String { self.hash.clone() } } + +#[cfg(test)] +mod test { + use std::path::PathBuf; + + use crate::utils::test_util; + + #[test] + fn test_save_and_load() { + test_util::setup_test_with_clean_mit(); + let test_data = "hello world"; + test_util::ensure_test_file(&PathBuf::from("a.txt"), Some(test_data)); + let blob = super::Blob::new(&PathBuf::from("a.txt")); + + let blob2 = super::Blob::load(&blob.hash); + assert_eq!(blob2.get_hash(), blob.get_hash()); + assert_eq!(blob2.data, test_data); + } +} diff --git a/src/models/tree.rs b/src/models/tree.rs index c8d7721..644c8d8 100644 --- a/src/models/tree.rs +++ b/src/models/tree.rs @@ -99,7 +99,7 @@ impl Tree { tree } - pub fn save(&mut self) -> String { + pub fn save(&mut self) -> Hash { let s = store::Store::new(); let tree_data = serde_json::to_string_pretty(&self).unwrap(); let hash = s.save(&tree_data); @@ -107,28 +107,6 @@ impl Tree { hash } - /**递归获取Tree对应的所有文件 */ - pub fn get_recursive_file_entries(&self) -> Vec { - let mut files = Vec::new(); - for entry in self.entries.iter() { - if entry.filemode.0 == "blob" { - files.push(PathBuf::from(entry.name.clone())); - } else { - let sub_tree = Tree::load(&entry.object_hash); - let sub_files = sub_tree.get_recursive_file_entries(); - - files.append( - sub_files - .iter() - .map(|file| PathBuf::from(entry.name.clone()).join(file)) - .collect::>() - .as_mut(), - ); - } - } - files - } - ///注:相对路径(to workdir) pub fn get_recursive_blobs(&self) -> Vec<(PathBuf, Hash)> { //TODO 返回HashMap @@ -159,7 +137,7 @@ mod test { use crate::{ models::*, - utils::{util, test_util}, + utils::test_util, }; #[test] @@ -198,34 +176,6 @@ mod test { assert!(tree.entries[1].name == loaded_tree.entries[1].name); } - #[test] - fn test_get_recursive_file_entries() { - test_util::setup_test_with_clean_mit(); - let index = Index::get_instance(); - let mut test_files = vec![PathBuf::from("b.txt"), PathBuf::from("mit_src/a.txt")]; - for test_file in test_files.clone() { - test_util::ensure_test_file(&test_file, None); - index.add(test_file.clone(), FileMetaData::new(&Blob::new(&test_file), &test_file)); - } - - let tree = Tree::new(&index); - let tree_hash = tree.get_hash(); - - let loaded_tree = Tree::load(&tree_hash); - let mut files = loaded_tree.get_recursive_file_entries(); - files.sort(); - test_files.sort(); - assert_eq!(files.len(), test_files.len()); - assert_eq!( - util::to_workdir_absolute_path(&files[0]).to_str().unwrap(), //TODO 罪大恶极的路径问题 - util::get_absolute_path(&test_files[0]).to_str().unwrap() - ); - assert_eq!( - util::to_workdir_absolute_path(&files[1]).to_str().unwrap(), - util::get_absolute_path(&test_files[1]).to_str().unwrap() - ); - } - #[test] fn test_get_recursive_blobs() { test_util::setup_test_with_clean_mit(); diff --git a/src/utils/store.rs b/src/utils/store.rs index f0441da..8170925 100644 --- a/src/utils/store.rs +++ b/src/utils/store.rs @@ -9,6 +9,9 @@ pub struct Store { store_path: PathBuf, } +/**Store负责管理objects + * 每一个object文件名与内容的hash值相同 + */ impl Store { pub fn new() -> Store { util::check_repo_exist(); @@ -26,13 +29,6 @@ impl Store { } } - pub fn contains(&self, hash: &String) -> bool { - let mut path = self.store_path.clone(); - path.push("objects"); - path.push(hash); - path.exists() - } - /// 将hash对应的文件内容(主要是blob)还原到file pub fn restore_to_file(&self, hash: &Hash, file: &PathBuf) { let content = self.load(hash); @@ -69,6 +65,7 @@ impl Store { } } + pub fn save(&self, content: &String) -> String { /* 保存文件内容 */ let hash = util::calc_hash(content); @@ -76,6 +73,10 @@ impl Store { path.push("objects"); path.push(&hash); // println!("Saved to: [{}]", path.display()); + if path.exists() { + // IO优化,文件已存在,不再写入 + return hash; + } match std::fs::write(path, content) { Ok(_) => hash, Err(_) => panic!("储存库疑似损坏,无法写入文件"),