mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 11:56:23 +08:00
@@ -34,12 +34,12 @@ use crate::translation_tasks::{
|
||||
};
|
||||
use crate::{
|
||||
build_official_pull_plan_for_platform_inventory, build_official_sync_plan,
|
||||
changed_endpoint_urls, default_official_platforms, DownloadError,
|
||||
changed_endpoint_urls, default_official_platforms, release_cas_reuse_references, DownloadError,
|
||||
OfficialGameMainConfigBootstrapService, OfficialLauncherBootstrapService,
|
||||
OfficialResourceHashVerification, OfficialResourcePullPlan, OfficialResourcePullProgress,
|
||||
OfficialResourcePullProgressKind, OfficialResourcePullService, OfficialResourceVerification,
|
||||
YostarJpLauncherCdnConfig, YostarJpLauncherGameConfig, YostarJpLauncherManifestUrl,
|
||||
YostarJpLauncherRemoteManifest,
|
||||
OfficialResourcePullProgressKind, OfficialResourcePullService, OfficialResourceReuseWarning,
|
||||
OfficialResourceVerification, YostarJpLauncherCdnConfig, YostarJpLauncherGameConfig,
|
||||
YostarJpLauncherManifestUrl, YostarJpLauncherRemoteManifest,
|
||||
};
|
||||
use crate::{
|
||||
read_parse_cache_at, OfficialParseCacheService, OfficialParseConfig, OfficialParseSummary,
|
||||
@@ -827,6 +827,18 @@ pub struct OfficialUpdateReport {
|
||||
pub resumed_count: usize,
|
||||
/// Number of resources skipped using the local manifest.
|
||||
pub skipped_count: usize,
|
||||
/// Number of resources reused from immutable historical releases.
|
||||
#[serde(default)]
|
||||
pub release_reused_count: usize,
|
||||
/// Number of resources reused from CAS.
|
||||
#[serde(default)]
|
||||
pub cas_reused_count: usize,
|
||||
/// Bytes materialized without network transfer.
|
||||
#[serde(default)]
|
||||
pub reused_bytes: u64,
|
||||
/// Diagnostics recorded while invalid reuse candidates fell back.
|
||||
#[serde(default)]
|
||||
pub reuse_warnings: Vec<OfficialResourceReuseWarning>,
|
||||
/// Final local byte count in the pull report.
|
||||
pub final_bytes: u64,
|
||||
/// Bytes transferred in this run.
|
||||
@@ -1120,6 +1132,36 @@ impl OfficialPublishLayout {
|
||||
path_exists_no_follow(&self.root.join(OFFICIAL_DOWNLOAD_MANIFEST_FILE))
|
||||
}
|
||||
|
||||
fn release_reuse_roots(&self, active_root: &Path) -> Result<Vec<PathBuf>, String> {
|
||||
let mut roots = Vec::new();
|
||||
if path_exists_no_follow(active_root)? {
|
||||
roots.push(active_root.to_path_buf());
|
||||
}
|
||||
if !path_exists_no_follow(&self.versions_dir)? {
|
||||
return Ok(roots);
|
||||
}
|
||||
ensure_safe_directory_path(&self.versions_dir, "官方资源历史 release 根目录")?;
|
||||
let mut versions = fs::read_dir(&self.versions_dir)
|
||||
.map_err(|error| {
|
||||
format!(
|
||||
"读取官方资源历史 release 根目录失败 {}:{error}",
|
||||
self.versions_dir.display()
|
||||
)
|
||||
})?
|
||||
.filter_map(|entry| entry.ok().map(|entry| entry.path()))
|
||||
.filter(|path| {
|
||||
fs::symlink_metadata(path)
|
||||
.map(|metadata| metadata.is_dir() && !metadata.file_type().is_symlink())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
versions.sort();
|
||||
versions.reverse();
|
||||
roots.extend(versions);
|
||||
roots.dedup();
|
||||
Ok(roots)
|
||||
}
|
||||
|
||||
fn publish(&self, plan: &OfficialPublishPlan) -> Result<PathBuf, String> {
|
||||
ensure_safe_directory_path(&self.versions_dir, "官方资源 versions 根目录")?;
|
||||
fs::create_dir_all(&self.versions_dir).map_err(|error| {
|
||||
@@ -1663,6 +1705,10 @@ impl OfficialUpdateService {
|
||||
downloaded_count: 0,
|
||||
resumed_count: 0,
|
||||
skipped_count: 0,
|
||||
release_reused_count: 0,
|
||||
cas_reused_count: 0,
|
||||
reused_bytes: 0,
|
||||
reuse_warnings: Vec::new(),
|
||||
final_bytes: 0,
|
||||
transferred_bytes: 0,
|
||||
official_seed_hash_verified_count: 0,
|
||||
@@ -1861,6 +1907,12 @@ impl OfficialUpdateService {
|
||||
&config.curl_command,
|
||||
)
|
||||
.with_proxy_config(config.curl_proxy.clone())
|
||||
.with_release_reuse_roots(
|
||||
publish_layout
|
||||
.release_reuse_roots(&active_resource_root)
|
||||
.map_err(anyhow::Error::msg)?,
|
||||
)
|
||||
.with_cas_reuse_root(config.effective_import_cas_root())
|
||||
.with_max_concurrency(config.download_concurrency);
|
||||
let pruned_stale_resource_count = staging_fetcher
|
||||
.prune_stale_manifest_entries(&pull_plan)
|
||||
@@ -1898,13 +1950,24 @@ impl OfficialUpdateService {
|
||||
progress(OfficialUpdateProgress::new(
|
||||
"download",
|
||||
format!(
|
||||
"下载阶段完成:已下载={} 已续传={} 已复用={} 本轮传输字节={}",
|
||||
"下载阶段完成:已下载={} 已续传={} 当前 manifest 复用={} 历史 release 复用={} CAS 复用={} 本轮传输字节={}",
|
||||
pull_report.downloaded_count(),
|
||||
pull_report.resumed_count(),
|
||||
pull_report.skipped_count(),
|
||||
pull_report.release_reused_count(),
|
||||
pull_report.cas_reused_count(),
|
||||
pull_report.transferred_bytes()
|
||||
),
|
||||
));
|
||||
if !pull_report.reuse_warnings.is_empty() {
|
||||
progress(OfficialUpdateProgress::new(
|
||||
"download",
|
||||
format!(
|
||||
"复用候选诊断:{} 项候选未通过校验,已按顺序回退",
|
||||
pull_report.reuse_warnings.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
progress(OfficialUpdateProgress::new(
|
||||
"audit",
|
||||
"执行最终本地 manifest 审计",
|
||||
@@ -1996,6 +2059,10 @@ impl OfficialUpdateService {
|
||||
report.downloaded_count = pull_report.downloaded_count();
|
||||
report.resumed_count = pull_report.resumed_count();
|
||||
report.skipped_count = pull_report.skipped_count();
|
||||
report.release_reused_count = pull_report.release_reused_count();
|
||||
report.cas_reused_count = pull_report.cas_reused_count();
|
||||
report.reused_bytes = pull_report.reused_bytes();
|
||||
report.reuse_warnings = pull_report.reuse_warnings.clone();
|
||||
report.final_bytes = pull_report.total_bytes();
|
||||
report.transferred_bytes = pull_report.transferred_bytes();
|
||||
report.official_seed_hash_verified_count = pull_report.official_hash_verified_count();
|
||||
@@ -2050,7 +2117,11 @@ impl OfficialUpdateService {
|
||||
|
||||
// 清理未被最新版本状态引用的孤儿 staging 目录(GC 失败仅告警,不影响发布结果)。
|
||||
match read_version_state(&version_state_path) {
|
||||
Ok(Some(state)) => match gc_orphan_staging(&config.output_root, &state) {
|
||||
Ok(Some(state)) => match gc_orphan_staging_with_cas_root(
|
||||
&config.output_root,
|
||||
&state,
|
||||
&config.effective_import_cas_root(),
|
||||
) {
|
||||
Ok(removed) if !removed.is_empty() => progress(OfficialUpdateProgress::new(
|
||||
"publish",
|
||||
format!("已清理 {} 个孤儿 staging 目录", removed.len()),
|
||||
@@ -2556,6 +2627,10 @@ fn waiting_for_official_resources_report(
|
||||
downloaded_count: 0,
|
||||
resumed_count: 0,
|
||||
skipped_count: 0,
|
||||
release_reused_count: 0,
|
||||
cas_reused_count: 0,
|
||||
reused_bytes: 0,
|
||||
reuse_warnings: Vec::new(),
|
||||
final_bytes: 0,
|
||||
transferred_bytes: 0,
|
||||
official_seed_hash_verified_count: 0,
|
||||
@@ -2874,6 +2949,8 @@ fn verification_progress_message(summary: &OfficialVerificationSummary) -> Strin
|
||||
fn localized_pull_status(status: crate::OfficialResourcePullStatus) -> &'static str {
|
||||
match status {
|
||||
crate::OfficialResourcePullStatus::SkippedExisting => "已复用",
|
||||
crate::OfficialResourcePullStatus::ReleaseReused => "已复用历史 release",
|
||||
crate::OfficialResourcePullStatus::CasReused => "已复用 CAS",
|
||||
crate::OfficialResourcePullStatus::Resumed => "已续传",
|
||||
crate::OfficialResourcePullStatus::Downloaded => "已下载",
|
||||
}
|
||||
@@ -3700,12 +3777,22 @@ pub fn gc_orphan_staging(
|
||||
output_root: &Path,
|
||||
state: &OfficialVersionState,
|
||||
) -> anyhow::Result<Vec<PathBuf>> {
|
||||
gc_orphan_staging_dirs(&output_root.join(OFFICIAL_STAGING_DIR), state)
|
||||
gc_orphan_staging_with_cas_root(output_root, state, &output_root.join(".cas"))
|
||||
}
|
||||
|
||||
/// Cleans orphan staging directories and releases their recorded CAS refs.
|
||||
pub fn gc_orphan_staging_with_cas_root(
|
||||
output_root: &Path,
|
||||
state: &OfficialVersionState,
|
||||
cas_root: &Path,
|
||||
) -> anyhow::Result<Vec<PathBuf>> {
|
||||
gc_orphan_staging_dirs(&output_root.join(OFFICIAL_STAGING_DIR), state, cas_root)
|
||||
}
|
||||
|
||||
fn gc_orphan_staging_dirs(
|
||||
staging_dir: &Path,
|
||||
state: &OfficialVersionState,
|
||||
cas_root: &Path,
|
||||
) -> anyhow::Result<Vec<PathBuf>> {
|
||||
let read_dir = match fs::read_dir(staging_dir) {
|
||||
Ok(read_dir) => read_dir,
|
||||
@@ -3740,6 +3827,7 @@ fn gc_orphan_staging_dirs(
|
||||
if referenced.contains(&name) {
|
||||
continue;
|
||||
}
|
||||
release_cas_reuse_references(&path, cas_root).map_err(anyhow::Error::msg)?;
|
||||
fs::remove_dir_all(&path).map_err(|error| {
|
||||
anyhow::anyhow!("清理孤儿 staging 目录失败 {}:{error}", path.display())
|
||||
})?;
|
||||
|
||||
Reference in New Issue
Block a user