mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-07-22 01:15:14 +08:00
refactor: address quality findings
This commit is contained in:
@@ -45,6 +45,9 @@ go.work.sum
|
||||
logs/
|
||||
pg_log/
|
||||
|
||||
# Generated reports
|
||||
/docs/reports/fuck-u-code-current.md
|
||||
|
||||
# Backups
|
||||
/deployments/backups/
|
||||
|
||||
|
||||
@@ -16,6 +16,59 @@ impl AddressablesCatalogDriver {
|
||||
pub fn new() -> 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 {
|
||||
@@ -64,69 +117,21 @@ impl ManifestDriver for AddressablesCatalogDriver {
|
||||
}
|
||||
|
||||
async fn parse(&self, raw_data: &[u8]) -> Result<GenericManifest, String> {
|
||||
// 解析 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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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<Vec<u8>> {
|
||||
self.storage.get(hash).await
|
||||
|
||||
@@ -117,6 +117,53 @@ impl FileSystemStorage {
|
||||
file.sync_all().await?;
|
||||
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]
|
||||
@@ -212,31 +259,9 @@ impl Storage for FileSystemStorage {
|
||||
|
||||
async fn list(&self) -> Result<Vec<Hash>> {
|
||||
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());
|
||||
|
||||
Reference in New Issue
Block a user