use std::{
collections::HashMap,
io::{Error, ErrorKind, Result},
path::{Path, PathBuf},
str::FromStr,
};
use git_internal::{
hash::ObjectHash,
internal::object::{blob::Blob, commit::Commit, tree::Tree, ObjectTrait},
};
use tokio::sync::mpsc::Receiver;
use crate::util::GPath;
pub trait TreeStore {
fn insert_tree(&self, path: PathBuf, tree: Tree);
fn get_bypath(&self, path: &Path) -> Result<Tree>;
fn db_tree_list(&self) -> Result<HashMap<PathBuf, Tree>>;
}
impl TreeStore for sled::Db {
fn insert_tree(&self, path: PathBuf, tree: Tree) {
let config = bincode::config::standard();
let value = bincode::encode_to_vec(&tree, config).unwrap();
let key = path.to_str().unwrap();
self.insert(key, value).unwrap();
}
fn get_bypath(&self, path: &Path) -> Result<Tree> {
let key = path.to_str().unwrap();
match self.get(key)? {
Some(encoded_value) => {
let config = bincode::config::standard();
let (decoded, _): (Tree, usize) =
bincode::decode_from_slice(&encoded_value, config)
.map_err(|_| std::io::Error::other("Deserialization error"))?;
Ok(decoded)
}
None => {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Path '{key}' not found"),
))
}
}
}
fn db_tree_list(&self) -> Result<HashMap<PathBuf, Tree>> {
self.iter()
.map(|item| match item {
Ok((path, encoded_value)) => {
let path = std::str::from_utf8(&path)
.map_err(|_| Error::new(ErrorKind::InvalidData, "Invalid UTF8 path"))?;
let config = bincode::config::standard();
let (decoded_tree, _): (Tree, usize) =
bincode::decode_from_slice(&encoded_value, config)
.map_err(|_| Error::other("Deserialization error"))?;
Ok((PathBuf::from(path), decoded_tree))
}
Err(e) => Err(Error::new(ErrorKind::NotFound, e)),
})
.collect::<Result<HashMap<PathBuf, Tree>>>()
}
}
#[allow(unused)]
pub trait CommitStore {
fn store_commit(&self, commit: Commit) -> Result<()>;
fn get_commit(&self) -> Result<Commit>;
}
impl CommitStore for sled::Db {
fn store_commit(&self, commit: Commit) -> Result<()> {
let config = bincode::config::standard();
let encoded_commit = bincode::encode_to_vec(&commit, config).unwrap();
let re = self.insert("COMMIT", encoded_commit)?;
if re.is_some() {
Ok(())
} else {
Err(std::io::Error::other("Failed to store commit"))
}
}
fn get_commit(&self) -> Result<Commit> {
let encoded_value = self.get("COMMIT")?;
let config = bincode::config::standard();
let (decoded, _): (Commit, usize) =
bincode::decode_from_slice(&encoded_value.unwrap(), config)
.map_err(|_| std::io::Error::other("Deserialization error"))?;
Ok(decoded)
}
}
pub async fn store_trees(storepath: &str, mut tree_channel: Receiver<(GPath, Tree)>) -> Result<()> {
let db = sled::open(storepath)?;
while let Some((path, tree)) = tree_channel.recv().await {
db.insert_tree(path.into(), tree);
}
Ok(())
}
fn format_data(data: &[u8]) -> Box<[u8]> {
let header: Vec<u8> = format!("blob {}", data.len()).into_bytes();
let mut res: Vec<u8> = Vec::with_capacity(header.len() + 1 + data.len());
res.extend(header);
res.push(b'\x00');
res.extend_from_slice(data);
Box::from(res.as_slice())
}
fn unformat_data(data: &[u8]) -> Vec<u8> {
match data.is_empty() {
true => Vec::new(),
false => {
let index: usize = data
.iter()
.position(|&b| b == b'\x00')
.expect("No null byte found in data");
data[(index + 1)..].to_vec()
}
}
}
pub trait BlobFsStore {
fn add_blob_to_hash(&self, hash: &str, blob: &[u8]) -> Result<()>;
fn get_blob_by_hash(&self, hash: &str) -> Result<Vec<u8>>;
fn list_blobs(&self, index_db: &sled::Db) -> Result<Vec<Blob>>;
}
impl BlobFsStore for PathBuf {
fn add_blob_to_hash(&self, hash: &str, blob: &[u8]) -> Result<()> {
match hash.len() {
40 => (),
_ => {
return Err(Error::new(
ErrorKind::InvalidInput,
"Hash must be 40 characters long",
))
}
};
let object_path = self.join("objects");
let hash_path = object_path.join(&hash[0..2]);
std::fs::create_dir_all(&hash_path)?;
let blob_path = hash_path.join(&hash[2..]);
let compressed_blob = format_data(blob);
std::fs::write(blob_path, compressed_blob)
}
fn get_blob_by_hash(&self, hash: &str) -> Result<Vec<u8>> {
match hash.len() {
40 => {
let object_path = self.join("objects");
let hash_path = object_path.join(&hash[0..2]);
let blob_path = hash_path.join(&hash[2..]);
let blob = std::fs::read(&blob_path)?;
Ok(unformat_data(&blob))
}
_ => Err(Error::new(
ErrorKind::InvalidInput,
"Hash must be 40 characters long",
)),
}
}
fn list_blobs(&self, index_db: &sled::Db) -> Result<Vec<Blob>> {
let hashmap = index_db.db_list()?;
let object_path = self.join("objects");
hashmap
.values()
.map(|hash| {
let hash_flag = hash.get(0..2).ok_or(Error::new(
ErrorKind::InvalidInput,
"Hash must be 40 characters long",
))?;
let hash_path = object_path.join(hash_flag);
let sha_hash = ObjectHash::from_str(hash)
.map_err(|e| Error::new(ErrorKind::InvalidInput, e))?;
let data_path = hash_path.join(&hash[2..]);
let data = std::fs::read(&data_path)?;
let blob = Blob::from_bytes(&data, sha_hash);
blob.map_err(|e| Error::new(ErrorKind::InvalidData, e))
})
.collect::<Result<Vec<Blob>>>()
}
}
pub trait ModifiedStore {
fn add(&self, path: PathBuf) -> Result<()>;
fn add_content(&self, path: PathBuf, content: &[u8]) -> Result<()>; fn path_list(&self) -> Result<Vec<PathBuf>>;
fn db_list(&self) -> Result<HashMap<PathBuf, String>>;
fn get_content(&self, path: PathBuf) -> Result<Vec<u8>>;
fn delete(&self, path: PathBuf) -> Result<bool>; }
impl ModifiedStore for sled::Db {
fn add(&self, path: PathBuf) -> Result<()> {
let key = path.to_str().unwrap();
self.insert(key, b"")?;
Ok(())
}
fn add_content(&self, path: PathBuf, content: &[u8]) -> Result<()> {
let key = path.to_str().unwrap();
self.insert(key, content)?;
Ok(())
}
fn path_list(&self) -> Result<Vec<PathBuf>> {
let mut paths = Vec::new();
for item in self.iter() {
let (key, _) = item?;
let key_str =
std::str::from_utf8(&key).map_err(|_| std::io::Error::other("Invalid UTF8"))?;
paths.push(PathBuf::from(key_str));
}
Ok(paths)
}
fn db_list(&self) -> Result<HashMap<PathBuf, String>> {
self.iter()
.map(|item| match item {
Ok((path, hash)) => {
let path = std::str::from_utf8(&path).unwrap_or("Invalid UTF8 path");
let hash = std::str::from_utf8(&hash)
.unwrap_or("Invalid UTF8 path")
.to_string();
Ok((PathBuf::from(path), hash))
}
Err(e) => Err(Error::other(e)),
})
.collect::<Result<HashMap<PathBuf, String>>>()
}
fn get_content(&self, path: PathBuf) -> Result<Vec<u8>> {
let key = path.to_str().unwrap();
if let Some(content) = self.get(key)? {
Ok(content.to_vec())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Path not found",
))
}
}
fn delete(&self, path: PathBuf) -> Result<bool> {
let key = path.to_str().unwrap();
let removed = self.remove(key)?;
Ok(removed.is_some())
}
}
pub struct TempStoreArea {
pub index_db: sled::Db,
pub rm_db: sled::Db,
}
impl TempStoreArea {
pub fn new(modified_path: &Path) -> Result<Self> {
let index_db = sled::open(modified_path.join("index.db"))?;
let rm_db = sled::open(modified_path.join("removedfile.db"))?;
Ok(Self { index_db, rm_db })
}
}
#[cfg(test)]
mod test {
use std::vec;
use git_internal::{
hash::ObjectHash,
internal::object::tree::{Tree, TreeItem, TreeItemMode},
};
#[test]
fn init_test_d() {
let db_path = "/tmp/init_test_d.db";
if std::path::Path::new(db_path).exists() {
std::fs::remove_file(db_path).ok();
}
let db = sled::open(db_path).unwrap();
let t = Tree::from_tree_items(vec![TreeItem::new(
TreeItemMode::Blob,
ObjectHash::new(&[4u8, 4u8, 4u8, 64u8, 84u8, 84u8]),
String::from("test"),
)])
.unwrap();
if let Some(encoded_value) = db.get(t.id.as_ref()).unwrap() {
let config = bincode::config::standard();
let decoded: Tree = bincode::decode_from_slice(&encoded_value, config)
.unwrap()
.0;
println!(" {decoded}");
};
}
#[test]
#[ignore = "manual test requiring specific database file"]
fn get_tree_test() {
let db_path =
std::env::var("TREE_DB_PATH").unwrap_or_else(|_| "/tmp/test_tree.db".to_string());
let db = sled::open(&db_path).unwrap();
let iter = db.iter();
for result in iter {
match result {
Ok((key, value)) => {
let config = bincode::config::standard();
let tree: Tree = bincode::decode_from_slice(&value, config).unwrap().0;
let key_str = std::str::from_utf8(&key).unwrap();
println!("path:{key_str}");
println!("{tree}");
}
Err(error) => {
println!("Error iterating over trees: {error}");
}
}
}
}
}