feat: add official sync progress logging

This commit is contained in:
2026-07-07 22:13:07 +08:00
parent b2d6871926
commit ea0920e4e3
10 changed files with 639 additions and 21 deletions
+145 -1
View File
@@ -24,6 +24,88 @@ pub enum OfficialResourcePullStatus {
Downloaded,
}
impl OfficialResourcePullStatus {
/// Returns a stable label for reports and progress logs.
pub fn as_str(self) -> &'static str {
match self {
Self::SkippedExisting => "skipped_existing",
Self::Resumed => "resumed",
Self::Downloaded => "downloaded",
}
}
}
/// Progress event kind emitted while executing an official pull plan.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OfficialResourcePullProgressKind {
/// A URL is about to be checked or downloaded.
Started,
/// A URL finished as skipped, resumed, or downloaded.
Finished,
}
impl OfficialResourcePullProgressKind {
/// Returns a stable label for progress logs.
pub fn as_str(self) -> &'static str {
match self {
Self::Started => "started",
Self::Finished => "finished",
}
}
}
/// Progress for one URL in an official pull plan.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OfficialResourcePullProgress {
/// Progress event kind.
pub kind: OfficialResourcePullProgressKind,
/// One-based URL index in the pull plan.
pub index: usize,
/// Total URL count in the pull plan.
pub total: usize,
/// Official URL currently being processed.
pub url: String,
/// Finished pull status, when `kind` is `Finished`.
pub status: Option<OfficialResourcePullStatus>,
/// Final local file size, when `kind` is `Finished`.
pub bytes: Option<u64>,
/// Bytes transferred during this run, when `kind` is `Finished`.
pub transferred_bytes: Option<u64>,
}
impl OfficialResourcePullProgress {
fn started(index: usize, total: usize, url: String) -> Self {
Self {
kind: OfficialResourcePullProgressKind::Started,
index,
total,
url,
status: None,
bytes: None,
transferred_bytes: None,
}
}
fn finished(
index: usize,
total: usize,
url: String,
status: OfficialResourcePullStatus,
bytes: u64,
transferred_bytes: u64,
) -> Self {
Self {
kind: OfficialResourcePullProgressKind::Finished,
index,
total,
url,
status: Some(status),
bytes: Some(bytes),
transferred_bytes: Some(transferred_bytes),
}
}
}
/// Official hash algorithm used by a verified resource sidecar.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OfficialResourceHashAlgorithm {
@@ -240,6 +322,16 @@ impl OfficialResourcePullService {
pub fn pull(
&self,
plan: &OfficialResourcePullPlan,
) -> Result<OfficialResourcePullReport, String> {
self.pull_with_progress(plan, |_| {})
}
/// Executes the plan and calls `progress` for every URL before and after it
/// is checked or downloaded.
pub fn pull_with_progress(
&self,
plan: &OfficialResourcePullPlan,
mut progress: impl FnMut(OfficialResourcePullProgress),
) -> Result<OfficialResourcePullReport, String> {
fs::create_dir_all(&self.output_root).map_err(|error| {
format!(
@@ -251,8 +343,17 @@ impl OfficialResourcePullService {
let official_hash_pairs = official_seed_hash_pairs(plan);
let force_refresh_urls = official_hash_refresh_urls(&official_hash_pairs);
let mut manifest = self.read_download_manifest()?;
let urls = plan.all_urls()?;
let total = urls.len();
let mut items = Vec::new();
for url in plan.all_urls()? {
for (offset, url) in urls.into_iter().enumerate() {
let index = offset + 1;
progress(OfficialResourcePullProgress::started(
index,
total,
url.clone(),
));
if !is_official_yostar_jp_url(&url) {
return Err(format!("Refusing to download non-official URL: {url}"));
}
@@ -282,6 +383,14 @@ impl OfficialResourcePullService {
result
};
progress(OfficialResourcePullProgress::finished(
index,
total,
url.clone(),
result.status,
result.bytes,
result.transferred_bytes,
));
items.push(OfficialResourcePullItem {
url,
destination,
@@ -1610,6 +1719,41 @@ fi
assert!(manifest.entries.contains_key(&report.items[0].url));
}
#[test]
fn pull_emits_started_and_finished_progress_events() {
let out_dir = TempDir::new().unwrap();
let bin_dir = TempDir::new().unwrap();
let curl_path = bin_dir.path().join("curl");
write_fake_curl(&curl_path);
let service = OfficialResourcePullService::with_curl_command(out_dir.path(), &curl_path);
let plan = build_official_pull_plan_for_platforms(
discovery_plan(),
inventory(),
&[PatchPlatform::Windows],
);
let expected_urls = plan.all_urls().unwrap().len();
let mut events = Vec::new();
let report = service
.pull_with_progress(&plan, |event| events.push(event))
.unwrap();
assert_eq!(report.items.len(), expected_urls);
assert_eq!(events.len(), expected_urls * 2);
assert_eq!(events[0].kind, OfficialResourcePullProgressKind::Started);
assert_eq!(events[0].index, 1);
assert_eq!(events[0].total, expected_urls);
assert_eq!(events[1].kind, OfficialResourcePullProgressKind::Finished);
assert_eq!(
events[1].status,
Some(OfficialResourcePullStatus::Downloaded)
);
assert!(events
.iter()
.any(|event| event.url.ends_with("/TableBundles/ExcelDB.db")));
}
#[test]
fn retries_transient_download_failures() {
let out_dir = TempDir::new().unwrap();