feat: improve bat daemon cli output

This commit is contained in:
2026-07-12 23:04:13 +08:00
parent 264e78c440
commit f7255f7c04
8 changed files with 3231 additions and 344 deletions
+72 -82
View File
@@ -87,7 +87,7 @@ impl Default for OfficialUpdateConfig {
launcher_version: "1.7.2".to_string(),
auto_discover: false,
platforms: None,
output_root: PathBuf::from("official_update_output"),
output_root: PathBuf::from("./bat-resources"),
snapshot_path: None,
curl_command: PathBuf::from("curl"),
unzip_command: PathBuf::from("unzip"),
@@ -442,7 +442,7 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"start",
format!(
"starting official resource sync: output={} dry_run={} auto_discover={}",
"开始官方资源同步:资源目录={} 试运行={} 自动发现={}",
config.output_root.display(),
config.dry_run,
config.auto_discover
@@ -451,18 +451,15 @@ impl OfficialUpdateService {
check_shutdown_requested(&mut should_cancel)?;
let _lock = if config.dry_run {
progress(OfficialUpdateProgress::new(
"lock",
"dry-run: skipping state lock",
));
progress(OfficialUpdateProgress::new("lock", "试运行:跳过状态锁"));
None
} else {
progress(OfficialUpdateProgress::new(
"lock",
format!("acquiring state lock {}", config.lock_path().display()),
format!("获取资源目录状态锁 {}", config.lock_path().display()),
));
let lock = OfficialUpdateLock::acquire(config)?;
progress(OfficialUpdateProgress::new("lock", "state lock acquired"));
progress(OfficialUpdateProgress::new("lock", "资源目录状态锁已获取"));
Some(lock)
};
check_shutdown_requested(&mut should_cancel)?;
@@ -483,7 +480,7 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"bootstrap",
format!(
"auto-discover enabled; launcher_version={} cache={}",
"启用自动发现;启动器版本={} 缓存={}",
config.launcher_version,
bootstrap_cache_path.display()
),
@@ -500,7 +497,7 @@ impl OfficialUpdateService {
} else {
progress(OfficialUpdateProgress::new(
"bootstrap",
"auto-discover disabled; using explicit metadata inputs",
"未启用自动发现;使用显式元数据输入",
));
None
};
@@ -515,7 +512,7 @@ impl OfficialUpdateService {
.map(|bootstrap| bootstrap.launcher_metadata.game_latest_version.clone())
})
.ok_or_else(|| {
anyhow::anyhow!("missing app version; pass it explicitly or enable auto-discover")
anyhow::anyhow!("缺少应用版本;请显式传入 --app-version 或启用 --auto-discover")
})?;
let connection_group = config
.connection_group
@@ -526,14 +523,12 @@ impl OfficialUpdateService {
})
})
.ok_or_else(|| {
anyhow::anyhow!(
"missing connection group; pass it explicitly or enable auto-discover"
)
anyhow::anyhow!("缺少连接组;请显式传入 --connection-group 或启用 --auto-discover")
})?;
progress(OfficialUpdateProgress::new(
"metadata",
format!(
"using app_version={} connection_group={} platforms={}",
"使用应用版本={} 连接组={} 平台={}",
app_version,
connection_group,
platforms_label(platforms)
@@ -543,35 +538,30 @@ impl OfficialUpdateService {
let server_info_bytes = if let Some(source) = config.server_info_source.as_ref() {
progress(OfficialUpdateProgress::new(
"server-info",
format!(
"loading server-info from {}",
server_info_source_label(source)
),
format!("{} 读取服务器信息", server_info_source_label(source)),
));
load_server_info(source, &fetcher)?
} else {
let bootstrap = bootstrap.as_ref().ok_or_else(|| {
anyhow::anyhow!(
"missing server-info source; pass one explicitly or enable auto-discover"
"缺少服务器信息来源;请显式传入 --server-info-* 或启用 --auto-discover"
)
})?;
let url = bootstrap
.game_main_config
.server_info_data_url
.as_deref()
.ok_or_else(|| {
anyhow::anyhow!("official GameMainConfig has no ServerInfoDataUrl")
})?;
.ok_or_else(|| anyhow::anyhow!("官方 GameMainConfig 中没有 ServerInfoDataUrl"))?;
progress(OfficialUpdateProgress::new(
"server-info",
format!("fetching discovered server-info {url}"),
format!("拉取自动发现的服务器信息 {url}"),
));
fetcher.fetch_bytes(url).map_err(anyhow::Error::msg)?
};
progress(OfficialUpdateProgress::new(
"server-info",
format!("parsing server-info bytes={}", server_info_bytes.len()),
format!("解析服务器信息,字节数={}", server_info_bytes.len()),
));
check_shutdown_requested(&mut should_cancel)?;
let server_info = YostarJpServerInfo::from_slice(&server_info_bytes)?;
@@ -581,7 +571,7 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"discovery",
format!(
"resolved addressables_root={} endpoints={}",
"解析到 Addressables={} endpoint={}",
current_snapshot.addressables_root,
current_snapshot.endpoints.len()
),
@@ -589,7 +579,7 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"markers",
format!(
"checking {} remote marker endpoints",
"检查 {} 个远端标记 endpoint",
marker_endpoint_count(&current_snapshot)
),
));
@@ -607,7 +597,7 @@ impl OfficialUpdateService {
check_shutdown_requested(&mut should_cancel)?;
progress(OfficialUpdateProgress::new(
"snapshot",
format!("reading previous snapshot {}", snapshot_path.display()),
format!("读取上次快照 {}", snapshot_path.display()),
));
let previous_snapshot = read_snapshot(&snapshot_path)?;
let previous_base_snapshot = previous_snapshot
@@ -623,13 +613,13 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"decision",
format!(
"remote decision={:?} force={} remote_should_download={}",
"远端决策={:?} 强制刷新={} 远端需要下载={}",
sync_plan.decision, config.force, remote_should_download
),
));
progress(OfficialUpdateProgress::new(
"plan",
"building official pull plan from latest seed catalogs",
"根据最新种子目录构建官方拉取计划",
));
let pull_plan = build_pull_plan(
&server_info,
@@ -643,7 +633,7 @@ impl OfficialUpdateService {
let url_count = pull_plan.all_urls().map_err(anyhow::Error::msg)?.len();
progress(OfficialUpdateProgress::new(
"plan",
format!("pull plan contains {url_count} official URLs"),
format!("拉取计划包含 {url_count} 个官方 URL"),
));
let local_state = fetcher
.local_resource_state(&pull_plan)
@@ -652,7 +642,7 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"local-state",
format!(
"manifest_entries={} existing_files={} has_local_resources={}",
"manifest 条目={} 已存在文件={} 是否已有本地资源={}",
local_state.manifest_entry_count,
local_state.existing_file_count,
has_local_resources
@@ -663,7 +653,7 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"audit",
format!(
"auditing local download manifest {}",
"审计本地下载 manifest {}",
fetcher.download_manifest_path().display()
),
));
@@ -675,13 +665,13 @@ impl OfficialUpdateService {
} else if config.audit_local {
progress(OfficialUpdateProgress::new(
"audit",
"no local resources found; skipping local audit before first full pull",
"未发现本地资源;首次全量拉取前跳过本地审计",
));
None
} else {
progress(OfficialUpdateProgress::new(
"audit",
"local manifest audit disabled",
"本地 manifest 审计已关闭",
));
None
};
@@ -703,14 +693,14 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"audit",
format!(
"local manifest verified={} repair_needed={}",
"本地 manifest 已校验={} 需要修复={}",
local_manifest_verified_count, local_manifest_repair_needed_count
),
));
progress(OfficialUpdateProgress::new(
"decision",
format!(
"final should_download={} repair_needed={} initial_pull_needed={}",
"最终决策 需要下载={} 需要修复={} 首次拉取={}",
should_download, repair_needed, initial_pull_needed
),
));
@@ -760,7 +750,7 @@ impl OfficialUpdateService {
if !should_download {
progress(OfficialUpdateProgress::new(
"finish",
"up to date; no download needed",
"资源已是最新;无需下载",
));
return Ok(report);
}
@@ -773,20 +763,20 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"dry-run",
format!(
"dry-run plan generated {} URLs",
"试运行已生成 {} URL",
report.download_url_count.unwrap_or(0)
),
));
} else {
progress(OfficialUpdateProgress::new(
"dry-run",
"dry-run detected pending download; full URL plan disabled",
"试运行检测到需要下载;未启用完整 URL 计划输出",
));
}
report.update_status = OfficialUpdateStatus::WouldDownload;
progress(OfficialUpdateProgress::new(
"finish",
"dry-run finished without writing state",
"试运行完成,未写入状态",
));
return Ok(report);
}
@@ -795,7 +785,7 @@ impl OfficialUpdateService {
let download_url_count = pull_plan.all_urls().map_err(anyhow::Error::msg)?.len();
progress(OfficialUpdateProgress::new(
"download",
format!("downloading or reusing {download_url_count} official URLs"),
format!("下载或复用 {download_url_count} 个官方 URL"),
));
let pull_report = fetcher
.pull_with_progress_and_cancellation(
@@ -809,7 +799,7 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"download",
format!(
"download phase finished: downloaded={} resumed={} skipped={} transferred_bytes={}",
"下载阶段完成:已下载={} 已续传={} 已复用={} 本轮传输字节={}",
pull_report.downloaded_count(),
pull_report.resumed_count(),
pull_report.skipped_count(),
@@ -818,7 +808,7 @@ impl OfficialUpdateService {
));
progress(OfficialUpdateProgress::new(
"audit",
"running final local manifest audit",
"执行最终本地 manifest 审计",
));
check_shutdown_requested(&mut should_cancel)?;
let final_audit = fetcher
@@ -826,7 +816,7 @@ impl OfficialUpdateService {
.map_err(anyhow::Error::msg)?;
progress(OfficialUpdateProgress::new(
"snapshot",
format!("writing snapshot {}", snapshot_path.display()),
format!("写入快照 {}", snapshot_path.display()),
));
write_snapshot(&snapshot_path, &current_update_snapshot)?;
@@ -845,7 +835,7 @@ impl OfficialUpdateService {
progress(OfficialUpdateProgress::new(
"finish",
format!(
"sync finished: resources={} official_hashes_verified={}",
"同步完成:资源数={} 官方 hash 已校验={}",
report.resource_count.unwrap_or(0),
report.official_seed_hash_verified_count
),
@@ -870,7 +860,7 @@ fn build_pull_plan(
progress(OfficialUpdateProgress::new(
"plan",
format!(
"discovery plan resolved: connection_group={} app_version={} endpoints={}",
"发现计划已解析:连接组={} 应用版本={} endpoint={}",
discovery.connection_group_name,
discovery.app_version,
discovery.endpoints.len()
@@ -881,7 +871,7 @@ fn build_pull_plan(
check_shutdown_requested(should_cancel)?;
progress(OfficialUpdateProgress::new(
"inventory",
"parsing seed catalogs into download inventory",
"正在把种子目录解析为下载清单",
));
let inventory = build_inventory_from_seed_catalogs(&seed_catalogs, platforms)?;
Ok(build_official_pull_plan_for_platform_inventory(
@@ -921,7 +911,7 @@ fn collect_endpoint_markers(
progress(OfficialUpdateProgress::new(
"marker",
format!(
"fetching {} marker{} {}",
"拉取 {} 标记{} {}",
endpoint_kind_label(endpoint.kind),
platform_suffix(endpoint.platform),
endpoint.url
@@ -946,9 +936,7 @@ fn collect_endpoint_markers(
fn check_shutdown_requested(should_cancel: &mut dyn FnMut() -> bool) -> anyhow::Result<()> {
if should_cancel() {
return Err(anyhow::anyhow!(
"official update interrupted by shutdown request"
));
return Err(anyhow::anyhow!("官方资源更新已被停止请求中断"));
}
Ok(())
@@ -978,11 +966,11 @@ fn marker_endpoint_count(snapshot: &YostarJpSyncSnapshot) -> usize {
fn server_info_source_label(source: &OfficialServerInfoSource) -> String {
match source {
OfficialServerInfoSource::LocalPath(path) => format!("local path {}", path.display()),
OfficialServerInfoSource::LocalPath(path) => format!("本地路径 {}", path.display()),
OfficialServerInfoSource::OfficialFile(file_name) => {
format!("official file {file_name}")
format!("官方文件 {file_name}")
}
OfficialServerInfoSource::OfficialUrl(url) => format!("official URL {url}"),
OfficialServerInfoSource::OfficialUrl(url) => format!("官方 URL {url}"),
}
}
@@ -1017,17 +1005,14 @@ fn progress_from_pull_event(event: OfficialResourcePullProgress) -> OfficialUpda
match event.kind {
OfficialResourcePullProgressKind::Started => OfficialUpdateProgress::new(
"download",
format!("({}/{}) started {}", event.index, event.total, event.url),
format!("({}/{}) 开始 {}", event.index, event.total, event.url),
),
OfficialResourcePullProgressKind::Finished => {
let status = event
.status
.map(|status| status.as_str())
.unwrap_or("unknown");
let status = event.status.map(localized_pull_status).unwrap_or("未知");
OfficialUpdateProgress::new(
"download",
format!(
"({}/{}) finished status={} bytes={} transferred_bytes={} {}",
"({}/{}) 完成 状态={} 文件字节={} 本轮传输字节={} {}",
event.index,
event.total,
status,
@@ -1040,6 +1025,14 @@ fn progress_from_pull_event(event: OfficialResourcePullProgress) -> OfficialUpda
}
}
fn localized_pull_status(status: crate::OfficialResourcePullStatus) -> &'static str {
match status {
crate::OfficialResourcePullStatus::SkippedExisting => "已复用",
crate::OfficialResourcePullStatus::Resumed => "已续传",
crate::OfficialResourcePullStatus::Downloaded => "已下载",
}
}
/// Computes the extended snapshot delta.
pub fn diff_extended_snapshot(
current: &OfficialUpdateSnapshot,
@@ -1074,7 +1067,7 @@ pub fn read_snapshot(path: &Path) -> anyhow::Result<Option<OfficialUpdateSnapsho
let legacy =
serde_json::from_slice::<YostarJpSyncSnapshot>(&data).map_err(|legacy_error| {
anyhow::anyhow!(
"failed to parse official update snapshot as v2 ({update_error}) or legacy v1 ({legacy_error})"
"无法解析官方更新快照:v2 解析失败 ({update_error})legacy v1 解析也失败 ({legacy_error})"
)
})?;
Ok(Some(OfficialUpdateSnapshot::new(legacy, Vec::new(), None)))
@@ -1178,7 +1171,7 @@ fn resolve_bootstrap(
);
progress(OfficialUpdateProgress::new(
"launcher",
"fetching official launcher game config and remote manifest",
"正在拉取官方启动器游戏配置和远端 manifest",
));
let (game_config, manifest_url, manifest) = launcher
.fetch_latest_remote_manifest()
@@ -1189,14 +1182,14 @@ fn resolve_bootstrap(
progress(OfficialUpdateProgress::new(
"launcher",
format!(
"launcher metadata resolved: latest_version={} manifest_files={}",
"启动器元数据已解析:最新版本={} manifest 文件数={}",
launcher_metadata.game_latest_version, launcher_metadata.manifest_file_count
),
));
progress(OfficialUpdateProgress::new(
"bootstrap-cache",
format!("checking bootstrap cache {}", cache_path.display()),
format!("检查启动缓存 {}", cache_path.display()),
));
if let Some(cache) = read_bootstrap_cache(cache_path)? {
if let Some(game_main_config) =
@@ -1204,7 +1197,7 @@ fn resolve_bootstrap(
{
progress(OfficialUpdateProgress::new(
"bootstrap-cache",
"cache hit; reusing parsed GameMainConfig",
"命中缓存;复用已解析的 GameMainConfig",
));
return Ok(ResolvedBootstrap {
launcher_metadata,
@@ -1216,7 +1209,7 @@ fn resolve_bootstrap(
progress(OfficialUpdateProgress::new(
"game-main-config",
"cache miss; fetching resources.assets and parsing GameMainConfig",
"缓存未命中;拉取 resources.assets 并解析 GameMainConfig",
));
check_shutdown_requested(should_cancel)?;
let bootstrapper = OfficialGameMainConfigBootstrapService::with_commands(
@@ -1249,12 +1242,12 @@ fn resolve_bootstrap(
)?;
progress(OfficialUpdateProgress::new(
"bootstrap-cache",
format!("bootstrap cache written {}", cache_path.display()),
format!("启动缓存已写入 {}", cache_path.display()),
));
} else {
progress(OfficialUpdateProgress::new(
"bootstrap-cache",
"dry-run: bootstrap cache not written",
"试运行:不写入启动缓存",
));
}
@@ -1287,7 +1280,7 @@ fn fetch_seed_catalogs(
progress(OfficialUpdateProgress::new(
"catalog",
format!(
"fetching seed catalog {}{} {}",
"拉取种子目录 {}{} {}",
endpoint_kind_label(endpoint.kind),
platform_suffix(endpoint.platform),
endpoint.url
@@ -1310,21 +1303,18 @@ fn build_inventory_from_seed_catalogs(
let table_catalog = catalogs
.table_catalog
.as_deref()
.ok_or_else(|| anyhow::anyhow!("official discovery did not include TableCatalog.bytes"))?;
.ok_or_else(|| anyhow::anyhow!("官方发现结果缺少 TableCatalog.bytes"))?;
let mut platform_catalogs = Vec::new();
for platform in platforms {
let bundle_packing_info = catalogs.bundle_packing_infos.get(platform).ok_or_else(|| {
anyhow::anyhow!(
"official discovery did not include {} BundlePackingInfo.bytes",
"官方发现结果缺少 {} BundlePackingInfo.bytes",
platform.as_str()
)
})?;
let media_catalog = catalogs.media_catalogs.get(platform).ok_or_else(|| {
anyhow::anyhow!(
"official discovery did not include {} MediaCatalog.bytes",
platform.as_str()
)
anyhow::anyhow!("官方发现结果缺少 {} MediaCatalog.bytes", platform.as_str())
})?;
platform_catalogs.push(YostarJpPlatformCatalogInventory::from_catalog_bytes(
@@ -1375,7 +1365,7 @@ impl SeedCatalogs {
fn required_platform(endpoint: &YostarJpResourceEndpoint) -> anyhow::Result<PatchPlatform> {
endpoint.platform.ok_or_else(|| {
anyhow::anyhow!(
"discovery endpoint {:?} has no platform: {}",
"发现 endpoint {:?} 缺少平台:{}",
endpoint.kind,
endpoint.url
)
@@ -1403,13 +1393,13 @@ impl OfficialUpdateLock {
continue;
}
return Err(anyhow::anyhow!(
"official update output is locked: {}",
"官方资源目录已被锁定 (locked){}",
path.display()
));
}
Err(error) => {
return Err(anyhow::anyhow!(
"failed to acquire official update lock {}: {error}",
"获取官方资源目录状态锁失败 {}{error}",
path.display()
));
}
@@ -1417,7 +1407,7 @@ impl OfficialUpdateLock {
}
Err(anyhow::anyhow!(
"failed to acquire official update lock {}",
"获取官方资源目录状态锁失败 {}",
path.display()
))
}
@@ -1639,7 +1629,7 @@ mod tests {
let first = OfficialUpdateLock::acquire(&config).unwrap();
let second = OfficialUpdateLock::acquire(&config).unwrap_err();
assert!(second.to_string().contains("locked"));
assert!(second.to_string().contains("锁定"));
drop(first);
assert!(OfficialUpdateLock::acquire(&config).is_ok());
}
@@ -1681,7 +1671,7 @@ mod tests {
)
.unwrap_err();
assert!(error.to_string().contains("shutdown request"));
assert!(error.to_string().contains("停止请求"));
assert_eq!(checks, 1);
}
}