Skip to main content

pacenotes_installer\worker/
download_run.rs

1/// A 64 KiB chunk arrives hundreds of times a second, and a post per chunk
2/// would keep the UI thread redrawing instead of responding.
3const REPORT_EVERY: std::time::Duration = std::time::Duration::from_millis(100);
4
5/// Everything scene 4b's run needs, owned, because it crosses a thread. It
6/// knows no game folder: the download only fills the cache.
7pub struct DownloadJob {
8    pub base_url: String,
9    pub token: Option<String>,
10    pub cache_root: std::path::PathBuf,
11    /// Checked in scene 4 and not on this computer yet, which is the scene 4
12    /// selection rather than the scene 5 picks.
13    pub downloads: Vec<pacenotes_shared::PackageInfo>,
14    pub cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
15}
16
17/// Scene 4b's run: fetch what is missing and verify it, writing nothing to the
18/// game folder, so the cache can be filled now and installed from later. A
19/// package that still fails after its retries is recorded and the rest carry
20/// on. The worker opens its own handle on the cache; the UI reopens its own
21/// afterwards.
22pub fn run_download(job: DownloadJob, reporter: &super::Reporter) -> super::Message {
23    let api = crate::api::Api::new(&job.base_url, job.token.clone());
24    let mut cache = pacenotes_installer::cache::Cache::open(&job.cache_root);
25    let missing = &job.downloads;
26    let total: u64 = missing.iter().map(|package| package.size).sum();
27    let mut done: u64 = 0;
28    let mut downloaded = 0;
29    let mut bytes: u64 = 0;
30    let mut failed = Vec::new();
31
32    for package in missing {
33        let report = |item_done: u64, log: Option<String>| {
34            reporter.send(super::Progress {
35                phase: super::Phase::Download,
36                item: package.label().to_string(),
37                item_done,
38                item_size: package.size,
39                done: done + item_done,
40                total,
41                log,
42            });
43        };
44        report(
45            0,
46            Some(format!(
47                "downloading {} ({})",
48                package.download_path(),
49                pacenotes_installer::size::human(package.size)
50            )),
51        );
52
53        let mut last_post = std::time::Instant::now();
54        let mut progress = |read: u64, _content_length: Option<u64>| {
55            if last_post.elapsed() >= REPORT_EVERY {
56                last_post = std::time::Instant::now();
57                report(read, None);
58            }
59        };
60        let mut on_retry = |error: &crate::http::HttpError, delay_ms: u64| {
61            let line = format!(
62                "retrying {} in {} s: {}",
63                package.download_path(),
64                delay_ms / 1000,
65                error.message
66            );
67            report(0, Some(line));
68        };
69        match crate::package_download::download_retrying(
70            &api,
71            package,
72            &mut cache,
73            &mut progress,
74            &job.cancel,
75            &mut on_retry,
76        ) {
77            Ok(()) => {
78                downloaded += 1;
79                bytes += package.size;
80            }
81            Err(e) if e.is_cancelled() => {
82                return super::Message::Downloaded(super::Fetched::Cancelled)
83            }
84            Err(e) => failed.push(super::NotDownloaded {
85                key: package.download_path(),
86                label: package.label().to_string(),
87                reason: e.message,
88            }),
89        }
90        // Counted either way, so the bar reaches its end.
91        done += package.size;
92    }
93
94    super::Message::Downloaded(super::Fetched::Done {
95        downloaded,
96        bytes,
97        failed,
98    })
99}