refactor: address quality findings

This commit is contained in:
2026-06-28 13:04:53 +08:00
parent b202d7b231
commit 02716d3ccd
4 changed files with 120 additions and 78 deletions
+3
View File
@@ -45,6 +45,9 @@ go.work.sum
logs/ logs/
pg_log/ pg_log/
# Generated reports
/docs/reports/fuck-u-code-current.md
# Backups # Backups
/deployments/backups/ /deployments/backups/
+57 -52
View File
@@ -16,6 +16,59 @@ impl AddressablesCatalogDriver {
pub fn new() -> Self { pub fn new() -> Self {
Self Self
} }
fn parse_json(raw_data: &[u8]) -> Result<Value, String> {
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<String> {
json.get("m_LocatorId")
.and_then(|value| value.as_str())
.map(ToOwned::to_owned)
}
fn cdn_prefixes(json: &Value) -> Vec<String> {
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<ResourceEntry> {
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 { impl Default for AddressablesCatalogDriver {
@@ -64,69 +117,21 @@ impl ManifestDriver for AddressablesCatalogDriver {
} }
async fn parse(&self, raw_data: &[u8]) -> Result<GenericManifest, String> { async fn parse(&self, raw_data: &[u8]) -> Result<GenericManifest, String> {
// 解析 JSON let json = Self::parse_json(raw_data)?;
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,
});
}
}
}
// TODO: 解析 m_KeyDataString、m_EntryDataString 等压缩字段 // TODO: 解析 m_KeyDataString、m_EntryDataString 等压缩字段
// 这些字段使用了自定义压缩格式,需要实现解压缩算法 // 这些字段使用了自定义压缩格式,需要实现解压缩算法
// 参考:Unity Addressables 源代码 // 参考:Unity Addressables 源代码
let metadata = ManifestMetadata { let metadata = ManifestMetadata {
locator_id, locator_id: Self::locator_id(&json),
cdn_prefixes, cdn_prefixes: Self::cdn_prefixes(&json),
extra: HashMap::new(), extra: HashMap::new(),
}; };
Ok(GenericManifest { Ok(GenericManifest {
format: ManifestFormat::AddressablesCatalog, format: ManifestFormat::AddressablesCatalog,
resources, resources: Self::resources(&json),
metadata, metadata,
}) })
} }
+11 -2
View File
@@ -46,7 +46,8 @@ impl FileSystemCasRepository {
.await .await
{ {
if !existed { if !existed {
let _ = self.storage.delete(&stored_hash).await; self.delete_new_object_after_metadata_failure(&stored_hash)
.await?;
} }
return Err(error); return Err(error);
} }
@@ -58,13 +59,21 @@ impl FileSystemCasRepository {
} }
Err(error) => { Err(error) => {
if !existed { if !existed {
let _ = self.storage.delete(&stored_hash).await; self.delete_new_object_after_metadata_failure(&stored_hash)
.await?;
} }
Err(error) 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。 /// 读取对象并验证 Hash。
pub async fn get(&self, hash: &Hash) -> Result<Vec<u8>> { pub async fn get(&self, hash: &Hash) -> Result<Vec<u8>> {
self.storage.get(hash).await self.storage.get(hash).await
+49 -24
View File
@@ -117,6 +117,53 @@ impl FileSystemStorage {
file.sync_all().await?; file.sync_all().await?;
Ok(()) Ok(())
} }
async fn child_directories(path: &Path) -> Result<Vec<PathBuf>> {
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<Vec<Hash>> {
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<Option<Hash>> {
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<Vec<PathBuf>> {
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] #[async_trait]
@@ -212,31 +259,9 @@ impl Storage for FileSystemStorage {
async fn list(&self) -> Result<Vec<Hash>> { async fn list(&self) -> Result<Vec<Hash>> {
let mut hashes = Vec::new(); 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? { for shard_directory in self.object_shard_directories().await? {
if !first_entry.file_type().await?.is_dir() { hashes.extend(Self::hashes_in_directory(&shard_directory).await?);
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);
}
}
}
}
} }
hashes.sort_by_key(|hash: &Hash| hash.to_string()); hashes.sort_by_key(|hash: &Hash| hash.to_string());