fix(sync): 修复官方资源并发下载与媒体路径
bat-rust / Build and test Rust (push) Successful in 3m47s

daemon 子进程现会透传下载并发配置,下载进度按已完成数量单调上报。MediaCatalog 改为使用官方相对路径生成全局媒体 URL,覆盖 GameData、Prologue 等目录并补齐 jpg 资源,避免叶子文件名误拼媒体根目录导致 403。同步更新测试、smoke 断言和运行文档。
This commit is contained in:
2026-07-23 11:56:34 +08:00
parent d694100d6c
commit 3f5d2a8da7
16 changed files with 182 additions and 74 deletions
+28 -16
View File
@@ -118,7 +118,12 @@ impl OfficialResourcePullProgressKind {
pub struct OfficialResourcePullProgress {
/// Progress event kind.
pub kind: OfficialResourcePullProgressKind,
/// One-based URL index in the pull plan.
/// Monotonic completed item count for the whole pull.
///
/// `Started` events report the currently completed count before the URL
/// finishes; `Finished` events report the count after completion. This is
/// intentionally not the URL's plan position, because concurrent downloads
/// finish out of plan order and status percentages must not move backward.
pub index: usize,
/// Total URL count in the pull plan.
pub total: usize,
@@ -633,6 +638,7 @@ impl OfficialResourcePullService {
.collect();
let mut download_results: Vec<Option<Result<PullOneResult, PullOneError>>> =
(0..planned.len()).map(|_| None).collect();
let mut completed_count = 0usize;
if !download_indices.is_empty() {
let concurrency = self.download_concurrency.clamp(1, MAX_DOWNLOAD_CONCURRENCY);
@@ -680,7 +686,7 @@ impl OfficialResourcePullService {
WorkerMessage::Started { plan_index } => {
let item = &planned[plan_index];
progress(OfficialResourcePullProgress::started(
plan_index + 1,
completed_count,
total,
item.url.clone(),
));
@@ -711,8 +717,9 @@ impl OfficialResourcePullService {
)));
continue;
}
completed_count += 1;
progress(OfficialResourcePullProgress::finished(
plan_index + 1,
completed_count,
total,
item.url.clone(),
pull_result.status,
@@ -736,7 +743,7 @@ impl OfficialResourcePullService {
let item = &planned[plan_index];
self.record_quarantine_entry(&item.url, &item.destination, error)?;
progress(OfficialResourcePullProgress::failed(
plan_index + 1,
completed_count,
total,
item.url.clone(),
error,
@@ -768,12 +775,13 @@ impl OfficialResourcePullService {
let result = if let Some(existing) = &item.existing {
self.clear_quarantine_entry(&item.url)?;
progress(OfficialResourcePullProgress::started(
plan_index + 1,
completed_count,
total,
item.url.clone(),
));
completed_count += 1;
progress(OfficialResourcePullProgress::finished(
plan_index + 1,
completed_count,
total,
item.url.clone(),
existing.status,
@@ -2083,7 +2091,7 @@ exit 22
YostarJpDownloadInventory::from_catalog_bytes(
b"FullPatch_000.zip",
b"ExcelDB.db ExcelDB.db",
b"JP_Airi.zip",
b"GameData\\Audio\\VOC_JP\\JP_Airi.zip",
)
}
@@ -2143,10 +2151,10 @@ done
printf '%s' '{android_media_hash}' > "$out"
;;
*/MediaResources-Windows/Catalog/MediaCatalog.bytes)
printf '%s' 'JP_Airi_Win.zip' > "$out"
printf '%s' 'GameData\Audio\VOC_JP\JP_Airi_Win.zip' > "$out"
;;
*/MediaResources/Catalog/MediaCatalog.bytes)
printf '%s' 'JP_Airi_Android.zip' > "$out"
printf '%s' 'GameData\Audio\VOC_JP\JP_Airi_Android.zip' > "$out"
;;
*.zip)
printf '\120\113\003\004\024\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\002\000\000\000\010\000\000\000file.txtok\120\113\001\002\024\000\024\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\002\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000file.txt\120\113\005\006\000\000\000\000\001\000\001\000\066\000\000\000\050\000\000\000\000\000' > "$out"
@@ -2162,8 +2170,8 @@ fi
table_catalog_hash = xxhash32(b"ExcelDB.db"),
windows_bundle_hash = xxhash32(b"FullPatch_000.zip"),
android_bundle_hash = xxhash32(b"FullPatch_001.zip"),
windows_media_hash = xxhash32(b"JP_Airi_Win.zip"),
android_media_hash = xxhash32(b"JP_Airi_Android.zip"),
windows_media_hash = xxhash32(b"GameData\\Audio\\VOC_JP\\JP_Airi_Win.zip"),
android_media_hash = xxhash32(b"GameData\\Audio\\VOC_JP\\JP_Airi_Android.zip"),
)
}
@@ -2868,7 +2876,10 @@ exit 22
assert_eq!(xxhash32(b""), 46947589);
assert_eq!(xxhash32(b"ExcelDB.db"), 2044170421);
assert_eq!(xxhash32(b"FullPatch_000.zip"), 4038880697);
assert_eq!(xxhash32(b"JP_Airi_Win.zip"), 1416778215);
assert_eq!(
xxhash32(b"GameData\\Audio\\VOC_JP\\JP_Airi_Win.zip"),
1865771294
);
assert_eq!(xxhash32(b"ExcelDB.db ExcelDB.db"), 2147704569);
}
@@ -3053,11 +3064,12 @@ exit 22
assert_eq!(started.len(), expected_urls);
assert_eq!(finished.len(), expected_urls);
// 每个 started 的 total 一致;index 覆盖 1..=N 且不重复
let mut indices: Vec<usize> = started.iter().map(|event| event.index).collect();
indices.sort_unstable();
assert_eq!(indices, (1..=expected_urls).collect::<Vec<_>>());
// 每个 started 的 total 一致;index 表示已完成数量,不能超过总数
assert!(started.iter().all(|event| event.index <= expected_urls));
assert!(started.iter().all(|event| event.total == expected_urls));
assert!(events.windows(2).all(|pair| pair[0].index <= pair[1].index));
let finished_indices: Vec<usize> = finished.iter().map(|event| event.index).collect();
assert_eq!(finished_indices, (1..=expected_urls).collect::<Vec<_>>());
// started 与 finished 覆盖同一组 URLfinished 均为已下载。
let started_urls: HashSet<_> = started.iter().map(|event| event.url.clone()).collect();