mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 10:04:55 +08:00
daemon 子进程现会透传下载并发配置,下载进度按已完成数量单调上报。MediaCatalog 改为使用官方相对路径生成全局媒体 URL,覆盖 GameData、Prologue 等目录并补齐 jpg 资源,避免叶子文件名误拼媒体根目录导致 403。同步更新测试、smoke 断言和运行文档。
This commit is contained in:
@@ -4471,6 +4471,8 @@ fn daemon_child_args(options: &CliOptions) -> Vec<String> {
|
||||
}
|
||||
args.push("--curl".to_string());
|
||||
args.push(config.curl_command.to_string_lossy().to_string());
|
||||
args.push("--download-concurrency".to_string());
|
||||
args.push(config.download_concurrency.to_string());
|
||||
match config.curl_proxy.mode() {
|
||||
CurlProxyMode::Auto if options.proxy_option_explicit => {
|
||||
args.push("--proxy".to_string());
|
||||
@@ -6324,6 +6326,8 @@ mod tests {
|
||||
"http://127.0.0.1:7890",
|
||||
"--unzip",
|
||||
"/usr/bin/unzip",
|
||||
"--download-concurrency",
|
||||
"40",
|
||||
"--interval",
|
||||
"30m",
|
||||
"--error-retry",
|
||||
@@ -6346,6 +6350,9 @@ mod tests {
|
||||
assert!(args
|
||||
.windows(2)
|
||||
.any(|pair| pair == ["--platforms", "Windows,Android"]));
|
||||
assert!(args
|
||||
.windows(2)
|
||||
.any(|pair| pair == ["--download-concurrency", "40"]));
|
||||
// 代理凭据不得进入子进程 argv:只放不含凭据的 flag,URL 经环境变量下传。
|
||||
assert!(args.contains(&PROXY_FROM_ENV_FLAG.to_string()));
|
||||
assert!(!args.iter().any(|arg| arg.contains("127.0.0.1:7890")));
|
||||
|
||||
@@ -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 覆盖同一组 URL;finished 均为已下载。
|
||||
let started_urls: HashSet<_> = started.iter().map(|event| event.url.clone()).collect();
|
||||
|
||||
@@ -186,7 +186,7 @@ mod tests {
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -197,12 +197,12 @@ mod tests {
|
||||
YostarJpPlatformCatalogInventory::from_catalog_bytes(
|
||||
PatchPlatform::Windows,
|
||||
b"FullPatch_000.zip",
|
||||
b"JP_Airi_Win.zip",
|
||||
b"GameData\\Audio\\VOC_JP\\JP_Airi_Win.zip",
|
||||
),
|
||||
YostarJpPlatformCatalogInventory::from_catalog_bytes(
|
||||
PatchPlatform::Android,
|
||||
b"FullPatch_001.zip",
|
||||
b"JP_Airi_Android.zip",
|
||||
b"GameData\\Audio\\VOC_JP\\JP_Airi_Android.zip",
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -229,11 +229,11 @@ mod tests {
|
||||
assert!(all_urls[0].ends_with("TableCatalog.bytes"));
|
||||
assert!(all_urls
|
||||
.iter()
|
||||
.any(|url| url.ends_with("/MediaResources-Windows/JP_Airi.zip")));
|
||||
.any(|url| url.ends_with("/MediaResources-Windows/GameData/Audio/VOC_JP/JP_Airi.zip")));
|
||||
assert_eq!(
|
||||
all_urls
|
||||
.iter()
|
||||
.filter(|url| url.ends_with("/MediaResources/JP_Airi.zip"))
|
||||
.filter(|url| url.ends_with("/MediaResources/GameData/Audio/VOC_JP/JP_Airi.zip"))
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
|
||||
@@ -1654,7 +1654,7 @@ fn progress_from_pull_event(event: OfficialResourcePullProgress) -> OfficialUpda
|
||||
OfficialResourcePullProgressKind::Started => OfficialUpdateProgress::new(
|
||||
"download",
|
||||
format!(
|
||||
"下载进度:总体 {}/{} ({:.1}%);单文件开始 URL={}",
|
||||
"下载进度:已完成 {}/{} ({:.1}%);单文件开始 URL={}",
|
||||
event.index,
|
||||
event.total,
|
||||
download_progress_percent(event.index, event.total),
|
||||
@@ -1667,7 +1667,7 @@ fn progress_from_pull_event(event: OfficialResourcePullProgress) -> OfficialUpda
|
||||
OfficialUpdateProgress::new(
|
||||
"download",
|
||||
format!(
|
||||
"下载进度:总体 {}/{} ({:.1}%);单文件完成 状态={} 文件字节={} 本轮传输字节={} URL={}",
|
||||
"下载进度:已完成 {}/{} ({:.1}%);单文件完成 状态={} 文件字节={} 本轮传输字节={} URL={}",
|
||||
event.index,
|
||||
event.total,
|
||||
download_progress_percent(event.index, event.total),
|
||||
@@ -1682,7 +1682,7 @@ fn progress_from_pull_event(event: OfficialResourcePullProgress) -> OfficialUpda
|
||||
OfficialResourcePullProgressKind::Failed => OfficialUpdateProgress::new(
|
||||
"download",
|
||||
format!(
|
||||
"下载中断:总体 {}/{} ({:.1}%);失败类型={} HTTP={} 可重试={} 尝试次数={} quarantine={};本轮跳过该 URL 且不会发布不完整资源 URL={}",
|
||||
"下载中断:已完成 {}/{} ({:.1}%);失败类型={} HTTP={} 可重试={} 尝试次数={} quarantine={};本轮跳过该 URL 且不会发布不完整资源 URL={}",
|
||||
event.index,
|
||||
event.total,
|
||||
download_progress_percent(event.index, event.total),
|
||||
|
||||
@@ -231,7 +231,7 @@ fn official_update_first_run_pulls_without_pre_audit() {
|
||||
.any(|event| { event.stage == "decision" && event.message.contains("首次拉取=true") }));
|
||||
assert!(events.iter().any(|event| {
|
||||
event.stage == "download"
|
||||
&& event.message.contains("下载进度:总体")
|
||||
&& event.message.contains("下载进度:已完成")
|
||||
&& event.message.contains("单文件")
|
||||
}));
|
||||
assert!(events.iter().any(|event| {
|
||||
@@ -561,7 +561,7 @@ elif [[ "$url" == "{addressables_root}/Windows_PatchPack/BundlePackingInfo.bytes
|
||||
elif [[ "$url" == "{addressables_root}/Windows_PatchPack/BundlePackingInfo.hash" ]]; then
|
||||
emit_text "{windows_bundle_catalog_hash}"
|
||||
elif [[ "$url" == "{addressables_root}/MediaResources-Windows/Catalog/MediaCatalog.bytes" ]]; then
|
||||
emit_text "JP_Airi_Win.zip"
|
||||
emit_text "GameData\\Audio\\VOC_JP\\JP_Airi_Win.zip"
|
||||
elif [[ "$url" == "{addressables_root}/MediaResources-Windows/Catalog/MediaCatalog.hash" ]]; then
|
||||
emit_text "{windows_media_catalog_hash}"
|
||||
elif [[ "$url" == "{addressables_root}/Android_PatchPack/BundlePackingInfo.bytes" ]]; then
|
||||
@@ -569,7 +569,7 @@ elif [[ "$url" == "{addressables_root}/Android_PatchPack/BundlePackingInfo.bytes
|
||||
elif [[ "$url" == "{addressables_root}/Android_PatchPack/BundlePackingInfo.hash" ]]; then
|
||||
emit_text "{android_bundle_catalog_hash}"
|
||||
elif [[ "$url" == "{addressables_root}/MediaResources/Catalog/MediaCatalog.bytes" ]]; then
|
||||
emit_text "JP_Airi_Android.zip"
|
||||
emit_text "GameData\\Audio\\VOC_JP\\JP_Airi_Android.zip"
|
||||
elif [[ "$url" == "{addressables_root}/MediaResources/Catalog/MediaCatalog.hash" ]]; then
|
||||
emit_text "{android_media_catalog_hash}"
|
||||
elif [[ -n "$output" && "$url" == "{addressables_root}/"* ]]; then
|
||||
@@ -601,9 +601,9 @@ fi
|
||||
addressables_root = TEST_ADDRESSABLES_ROOT,
|
||||
table_catalog_hash = xxhash32(b"ExcelDB.db ExcelDB.db"),
|
||||
windows_bundle_catalog_hash = xxhash32(b"FullPatch_000.zip"),
|
||||
windows_media_catalog_hash = xxhash32(b"JP_Airi_Win.zip"),
|
||||
windows_media_catalog_hash = xxhash32(b"GameData\\Audio\\VOC_JP\\JP_Airi_Win.zip"),
|
||||
android_bundle_catalog_hash = xxhash32(b"FullPatch_001.zip"),
|
||||
android_media_catalog_hash = xxhash32(b"JP_Airi_Android.zip"),
|
||||
android_media_catalog_hash = xxhash32(b"GameData\\Audio\\VOC_JP\\JP_Airi_Android.zip"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user