feat(sync): 接入解析缓存与汉化发布前置

补齐官方 release 解析缓存、TextUnit 明细索引、资源变更集、Crowdin handoff 预留、ResourceRepository 导入元数据和 localized release patch 前置链路。

同时开放文件级 patch.apply 与 UnityFS TextAsset/string/semantic field patch CLI/RPC 入口,并保留官方原版资源与汉化产物双目录发布状态。

验证:cargo test -p bat-assetbundle --locked;cargo clippy -p bat-assetbundle --all-targets --locked -- -D warnings;cargo test -p bat-infrastructure --locked。
This commit is contained in:
2026-07-31 00:38:45 +08:00
parent f4880a71bd
commit 2079c6a307
25 changed files with 17287 additions and 225 deletions
+27 -2
View File
@@ -2,9 +2,10 @@
use bat_adapters::manifest::GenericManifest;
use bat_adapters::unity::{RawAssetBundle, UnityAdapterRegistry};
use bat_core::domain::{Resource, ResourceEntry, ResourceType};
use bat_assetbundle::TextUnitExtractor;
use bat_core::domain::{Resource, ResourceEntry, ResourceMetadata, ResourceType};
use bat_core::repositories::{CasRepository, ResourceRepository};
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
/// 待导入的 AssetBundle 数据。
@@ -97,6 +98,12 @@ pub struct UnityFsImportSummary {
pub text_assets: Vec<String>,
/// 非致命 serialized-file 解析诊断数量。
pub serialized_parse_error_count: usize,
/// 从 TextAsset 和 TypeTree 字段提取出的 TextUnit 数量。
pub text_unit_count: usize,
/// TextUnit 格式标签。
pub text_unit_formats: Vec<String>,
/// TextUnit 提取阶段的非致命诊断数量。
pub text_unit_error_count: usize,
}
/// Manifest 导入报告。
@@ -208,6 +215,7 @@ impl<'a> ResourceImportService<'a> {
id: resource_id_for_path(&entry.path),
local_path: PathBuf::from(&entry.path),
entry: stored_entry,
metadata: ResourceMetadata::default(),
};
let id = self.resources.add(resource).await?;
added_resources.push(id.clone());
@@ -266,6 +274,14 @@ impl<'a> ResourceImportService<'a> {
manifest_path, error
))
})?;
let text_units = TextUnitExtractor::new().extract_bundle(&parsed, Some(manifest_path));
let text_unit_formats = text_units
.units
.iter()
.filter_map(|unit| unit.context.get("format").cloned())
.collect::<BTreeSet<_>>()
.into_iter()
.collect();
Ok(UnityFsImportSummary {
unity_version: parsed.unity_version,
@@ -285,6 +301,9 @@ impl<'a> ResourceImportService<'a> {
.map(|asset| asset.name)
.collect(),
serialized_parse_error_count: parsed.serialized_parse_errors.len(),
text_unit_count: text_units.units.len(),
text_unit_formats,
text_unit_error_count: text_units.errors.len(),
})
}
}
@@ -715,6 +734,9 @@ mod tests {
assert_eq!(unityfs.text_asset_count, 0);
assert!(unityfs.text_assets.is_empty());
assert_eq!(unityfs.serialized_parse_error_count, 0);
assert_eq!(unityfs.text_unit_count, 0);
assert!(unityfs.text_unit_formats.is_empty());
assert_eq!(unityfs.text_unit_error_count, 0);
assert_eq!(
report.imported[1].category,
ResourceImportCategory::TextAsset
@@ -799,6 +821,9 @@ mod tests {
assert_eq!(unityfs.text_asset_count, 1);
assert_eq!(unityfs.text_assets, vec!["Scenario".to_string()]);
assert_eq!(unityfs.serialized_parse_error_count, 0);
assert_eq!(unityfs.text_unit_count, 1);
assert_eq!(unityfs.text_unit_formats, vec!["plain".to_string()]);
assert_eq!(unityfs.text_unit_error_count, 0);
}
#[tokio::test]