chore: establish development baseline

This commit is contained in:
2026-06-28 01:27:09 +08:00
commit dd53e3054e
131 changed files with 19327 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
//! CAS 存储实现占位
+204
View File
@@ -0,0 +1,204 @@
//! CAS 文件系统实现
//!
//! 基于文件系统的内容寻址存储实现
use async_trait::async_trait;
use bat_core::repositories::cas_repository::{CasRepository, ObjectId};
use blake3::Hasher;
use std::path::PathBuf;
use tokio::fs;
use tokio::io::AsyncWriteExt;
/// 文件系统 CAS Repository
///
/// 使用文件系统存储对象,目录结构:
/// ```text
/// cas_root/
/// ├── objects/
/// │ ├── ab/
/// │ │ ├── cd/
/// │ │ │ └── abcd1234...
/// └── refs.db (SQLite)
/// ```
pub struct FileSystemCasRepository {
/// CAS 根目录
root: PathBuf,
}
impl FileSystemCasRepository {
/// 创建新的文件系统 CAS Repository
///
/// # 参数
///
/// - `root`: CAS 根目录
///
/// # 示例
///
/// ```rust,ignore
/// let repo = FileSystemCasRepository::new("/path/to/cas");
/// ```
pub fn new(root: impl Into<PathBuf>) -> Self {
Self {
root: root.into(),
}
}
/// 计算对象 Hash
fn compute_hash(data: &[u8]) -> ObjectId {
let mut hasher = Hasher::new();
hasher.update(data);
hasher.finalize().to_hex().to_string()
}
/// 获取对象路径
fn object_path(&self, id: &ObjectId) -> PathBuf {
let prefix1 = &id[0..2];
let prefix2 = &id[2..4];
self.root
.join("objects")
.join(prefix1)
.join(prefix2)
.join(id)
}
/// 初始化存储
pub async fn init(&self) -> bat_core::Result<()> {
fs::create_dir_all(self.root.join("objects"))
.await
.map_err(bat_core::Error::Io)?;
Ok(())
}
}
#[async_trait]
impl CasRepository for FileSystemCasRepository {
async fn store(&self, data: &[u8]) -> bat_core::Result<ObjectId> {
let hash = Self::compute_hash(data);
let path = self.object_path(&hash);
// 如果对象已存在,直接返回
if path.exists() {
return Ok(hash);
}
// 创建父目录
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.await
.map_err(bat_core::Error::Io)?;
}
// 写入对象
let mut file = fs::File::create(&path)
.await
.map_err(bat_core::Error::Io)?;
file.write_all(data)
.await
.map_err(bat_core::Error::Io)?;
file.sync_all()
.await
.map_err(bat_core::Error::Io)?;
Ok(hash)
}
async fn get(&self, id: &ObjectId) -> bat_core::Result<Vec<u8>> {
let path = self.object_path(id);
if !path.exists() {
return Err(bat_core::Error::NotFound(format!("Object not found: {}", id)));
}
let data = fs::read(&path)
.await
.map_err(bat_core::Error::Io)?;
// 验证 Hash
let computed_hash = Self::compute_hash(&data);
if &computed_hash != id {
return Err(bat_core::Error::InvalidArgument(format!(
"Hash mismatch: expected {}, got {}",
id, computed_hash
)));
}
Ok(data)
}
async fn exists(&self, id: &ObjectId) -> bool {
self.object_path(id).exists()
}
async fn add_reference(&self, _id: &ObjectId) -> bat_core::Result<u64> {
// TODO: Phase 1 Week 3 - 实现引用计数
Ok(1)
}
async fn remove_reference(&self, _id: &ObjectId) -> bat_core::Result<u64> {
// TODO: Phase 1 Week 3 - 实现引用计数
Ok(0)
}
async fn get_reference_count(&self, _id: &ObjectId) -> bat_core::Result<u64> {
// TODO: Phase 1 Week 3 - 实现引用计数
Ok(1)
}
async fn gc(&self) -> bat_core::Result<u64> {
// TODO: Phase 1 Week 3 - 实现垃圾回收
Ok(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[tokio::test]
async fn test_store_and_get() {
let temp_dir = TempDir::new().unwrap();
let repo = FileSystemCasRepository::new(temp_dir.path());
repo.init().await.unwrap();
let data = b"Hello, World!";
let id = repo.store(data).await.unwrap();
let retrieved = repo.get(&id).await.unwrap();
assert_eq!(data, retrieved.as_slice());
}
#[tokio::test]
async fn test_exists() {
let temp_dir = TempDir::new().unwrap();
let repo = FileSystemCasRepository::new(temp_dir.path());
repo.init().await.unwrap();
let data = b"Test data";
let id = repo.store(data).await.unwrap();
assert!(repo.exists(&id).await);
assert!(!repo.exists(&"nonexistent".to_string()).await);
}
#[tokio::test]
async fn test_deduplication() {
let temp_dir = TempDir::new().unwrap();
let repo = FileSystemCasRepository::new(temp_dir.path());
repo.init().await.unwrap();
let data = b"Duplicate data";
let id1 = repo.store(data).await.unwrap();
let id2 = repo.store(data).await.unwrap();
assert_eq!(id1, id2);
}
#[test]
fn test_compute_hash() {
let data = b"test";
let hash = FileSystemCasRepository::compute_hash(data);
assert!(!hash.is_empty());
assert_eq!(hash.len(), 64); // BLAKE3 produces 32 bytes = 64 hex chars
}
}
+16
View File
@@ -0,0 +1,16 @@
//! # BAT Infrastructure - 基础设施层
//!
//! 提供技术实现
//!
//! 包含:
//! - CAS 存储实现
//! - 下载器
//! - 解析器
#![warn(missing_docs)]
#![warn(clippy::all)]
pub mod cas;
/// Infrastructure 版本号
pub const VERSION: &str = env!("CARGO_PKG_VERSION");