mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-07-22 04:15:14 +08:00
chore: establish development baseline
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "bat-infrastructure"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bat-core = { path = "../core" }
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
blake3.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["test-util", "macros"] }
|
||||
tempfile = "3.14"
|
||||
@@ -0,0 +1 @@
|
||||
//! CAS 存储实现占位
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
Reference in New Issue
Block a user