From 02716d3ccd6ff39431eab02b2a3aba66633e5a9c Mon Sep 17 00:00:00 2001 From: Yuyi-Oak <1722157266@qq.com> Date: Sun, 28 Jun 2026 13:04:53 +0800 Subject: [PATCH] refactor: address quality findings --- .gitignore | 3 + adapters/src/manifest/addressables.rs | 109 +++++++++++++----------- crates/bat-cas-engine/src/repository.rs | 13 ++- crates/bat-cas-engine/src/storage.rs | 73 ++++++++++------ 4 files changed, 120 insertions(+), 78 deletions(-) diff --git a/.gitignore b/.gitignore index 9101889..18423e0 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,9 @@ go.work.sum logs/ pg_log/ +# Generated reports +/docs/reports/fuck-u-code-current.md + # Backups /deployments/backups/ diff --git a/adapters/src/manifest/addressables.rs b/adapters/src/manifest/addressables.rs index 6e40ee0..9afd645 100644 --- a/adapters/src/manifest/addressables.rs +++ b/adapters/src/manifest/addressables.rs @@ -16,6 +16,59 @@ impl AddressablesCatalogDriver { pub fn new() -> Self { Self } + + fn parse_json(raw_data: &[u8]) -> Result { + let text = std::str::from_utf8(raw_data).map_err(|e| format!("Invalid UTF-8: {}", e))?; + serde_json::from_str(text).map_err(|e| format!("Invalid JSON: {}", e)) + } + + fn locator_id(json: &Value) -> Option { + json.get("m_LocatorId") + .and_then(|value| value.as_str()) + .map(ToOwned::to_owned) + } + + fn cdn_prefixes(json: &Value) -> Vec { + json.get("m_InternalIdPrefixes") + .and_then(|value| value.as_array()) + .into_iter() + .flatten() + .filter_map(|value| value.as_str()) + .map(ToOwned::to_owned) + .collect() + } + + fn resource_type_for_path(path: &str) -> ResourceType { + if path.ends_with(".bundle") { + ResourceType::AssetBundle + } else if path.contains("catalog") { + ResourceType::Manifest + } else { + ResourceType::Other + } + } + + fn resources(json: &Value) -> Vec { + json.get("m_InternalIds") + .and_then(|value| value.as_array()) + .into_iter() + .flatten() + .enumerate() + .filter_map(|(index, value)| { + let path = value.as_str()?; + if path.is_empty() { + return None; + } + + Some(ResourceEntry { + path: path.to_string(), + hash: format!("addressable_{}", index), + size: 0, + resource_type: Self::resource_type_for_path(path), + }) + }) + .collect() + } } impl Default for AddressablesCatalogDriver { @@ -64,69 +117,21 @@ impl ManifestDriver for AddressablesCatalogDriver { } async fn parse(&self, raw_data: &[u8]) -> Result { - // 解析 JSON - let text = std::str::from_utf8(raw_data).map_err(|e| format!("Invalid UTF-8: {}", e))?; - - let json: Value = serde_json::from_str(text).map_err(|e| format!("Invalid JSON: {}", e))?; - - // 提取元数据 - let locator_id = json - .get("m_LocatorId") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()); - - // 提取 CDN 前缀 - let mut cdn_prefixes = Vec::new(); - if let Some(prefixes) = json.get("m_InternalIdPrefixes").and_then(|v| v.as_array()) { - for prefix in prefixes { - if let Some(s) = prefix.as_str() { - cdn_prefixes.push(s.to_string()); - } - } - } - - // 提取资源列表(基础版本 - 仅从 m_InternalIds 提取) - let mut resources = Vec::new(); - if let Some(internal_ids) = json.get("m_InternalIds").and_then(|v| v.as_array()) { - for (index, id) in internal_ids.iter().enumerate() { - if let Some(path) = id.as_str() { - // 跳过空路径 - if path.is_empty() { - continue; - } - - // 判断资源类型 - let resource_type = if path.ends_with(".bundle") { - ResourceType::AssetBundle - } else if path.contains("catalog") { - ResourceType::Manifest - } else { - ResourceType::Other - }; - - resources.push(ResourceEntry { - path: path.to_string(), - hash: format!("addressable_{}", index), // 临时 Hash - size: 0, // 大小未知 - resource_type, - }); - } - } - } + let json = Self::parse_json(raw_data)?; // TODO: 解析 m_KeyDataString、m_EntryDataString 等压缩字段 // 这些字段使用了自定义压缩格式,需要实现解压缩算法 // 参考:Unity Addressables 源代码 let metadata = ManifestMetadata { - locator_id, - cdn_prefixes, + locator_id: Self::locator_id(&json), + cdn_prefixes: Self::cdn_prefixes(&json), extra: HashMap::new(), }; Ok(GenericManifest { format: ManifestFormat::AddressablesCatalog, - resources, + resources: Self::resources(&json), metadata, }) } diff --git a/crates/bat-cas-engine/src/repository.rs b/crates/bat-cas-engine/src/repository.rs index 46b8ef6..0975dc2 100644 --- a/crates/bat-cas-engine/src/repository.rs +++ b/crates/bat-cas-engine/src/repository.rs @@ -46,7 +46,8 @@ impl FileSystemCasRepository { .await { if !existed { - let _ = self.storage.delete(&stored_hash).await; + self.delete_new_object_after_metadata_failure(&stored_hash) + .await?; } return Err(error); } @@ -58,13 +59,21 @@ impl FileSystemCasRepository { } Err(error) => { if !existed { - let _ = self.storage.delete(&stored_hash).await; + self.delete_new_object_after_metadata_failure(&stored_hash) + .await?; } Err(error) } } } + async fn delete_new_object_after_metadata_failure(&self, hash: &Hash) -> Result<()> { + match self.storage.delete(hash).await { + Ok(()) | Err(CasError::ObjectNotFound(_)) => Ok(()), + Err(error) => Err(error), + } + } + /// 读取对象并验证 Hash。 pub async fn get(&self, hash: &Hash) -> Result> { self.storage.get(hash).await diff --git a/crates/bat-cas-engine/src/storage.rs b/crates/bat-cas-engine/src/storage.rs index 613fd8e..99341b3 100644 --- a/crates/bat-cas-engine/src/storage.rs +++ b/crates/bat-cas-engine/src/storage.rs @@ -117,6 +117,53 @@ impl FileSystemStorage { file.sync_all().await?; Ok(()) } + + async fn child_directories(path: &Path) -> Result> { + let mut directories = Vec::new(); + let mut entries = tokio::fs::read_dir(path).await?; + + while let Some(entry) = entries.next_entry().await? { + if entry.file_type().await?.is_dir() { + directories.push(entry.path()); + } + } + + Ok(directories) + } + + async fn hashes_in_directory(path: &Path) -> Result> { + let mut hashes = Vec::new(); + let mut entries = tokio::fs::read_dir(path).await?; + + while let Some(entry) = entries.next_entry().await? { + if let Some(hash) = Self::hash_from_file_entry(entry).await? { + hashes.push(hash); + } + } + + Ok(hashes) + } + + async fn hash_from_file_entry(entry: tokio::fs::DirEntry) -> Result> { + if !entry.file_type().await?.is_file() { + return Ok(None); + } + + Ok(entry + .file_name() + .to_str() + .and_then(|name| name.parse().ok())) + } + + async fn object_shard_directories(&self) -> Result> { + let mut shard_directories = Vec::new(); + + for first_level in Self::child_directories(&self.objects_dir).await? { + shard_directories.extend(Self::child_directories(&first_level).await?); + } + + Ok(shard_directories) + } } #[async_trait] @@ -212,31 +259,9 @@ impl Storage for FileSystemStorage { async fn list(&self) -> Result> { let mut hashes = Vec::new(); - let mut first_level = tokio::fs::read_dir(&self.objects_dir).await?; - while let Some(first_entry) = first_level.next_entry().await? { - if !first_entry.file_type().await?.is_dir() { - continue; - } - - let mut second_level = tokio::fs::read_dir(first_entry.path()).await?; - while let Some(second_entry) = second_level.next_entry().await? { - if !second_entry.file_type().await?.is_dir() { - continue; - } - - let mut files = tokio::fs::read_dir(second_entry.path()).await?; - while let Some(file_entry) = files.next_entry().await? { - if !file_entry.file_type().await?.is_file() { - continue; - } - if let Some(file_name) = file_entry.file_name().to_str() { - if let Ok(hash) = file_name.parse() { - hashes.push(hash); - } - } - } - } + for shard_directory in self.object_shard_directories().await? { + hashes.extend(Self::hashes_in_directory(&shard_directory).await?); } hashes.sort_by_key(|hash: &Hash| hash.to_string());