mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-07-22 04:15:14 +08:00
refactor: address quality findings
This commit is contained in:
@@ -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/
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
Reference in New Issue
Block a user