pacenotes_installer/
package_download.rs1const RETRY_DELAYS_MS: [u64; 2] = [1_000, 3_000];
6const CANCEL_POLL_MS: u64 = 100;
8
9struct Failure {
11 error: crate::http::HttpError,
12 retry: bool,
13}
14
15impl Failure {
16 fn transfer(error: crate::http::HttpError) -> Failure {
17 let retry = error.is_transient();
18 Failure { error, retry }
19 }
20
21 fn local(context: &str, error: std::io::Error) -> Failure {
23 Failure {
24 error: crate::http::HttpError::parse(format!("{context}: {error}")),
25 retry: false,
26 }
27 }
28}
29
30pub fn download_retrying(
34 api: &crate::api::Api,
35 package: &pacenotes_shared::PackageInfo,
36 cache: &mut pacenotes_installer::cache::Cache,
37 progress: crate::http::ProgressFn,
38 cancel: &std::sync::atomic::AtomicBool,
39 on_retry: &mut dyn FnMut(&crate::http::HttpError, u64),
40) -> Result<(), crate::http::HttpError> {
41 let mut delays = RETRY_DELAYS_MS.iter();
42 loop {
43 let failure = match download_to_cache(api, package, cache, &mut *progress, cancel) {
44 Ok(()) => return Ok(()),
45 Err(failure) => failure,
46 };
47 let delay = match delays.next() {
48 Some(&delay) if failure.retry && !failure.error.is_cancelled() => delay,
49 _ => return Err(failure.error),
50 };
51 on_retry(&failure.error, delay);
52 wait(delay, cancel)?;
53 }
54}
55
56fn wait(
58 milliseconds: u64,
59 cancel: &std::sync::atomic::AtomicBool,
60) -> Result<(), crate::http::HttpError> {
61 let mut waited = 0;
62 while waited < milliseconds {
63 if cancel.load(std::sync::atomic::Ordering::Relaxed) {
64 return Err(crate::http::HttpError::cancelled());
65 }
66 std::thread::sleep(std::time::Duration::from_millis(CANCEL_POLL_MS));
67 waited += CANCEL_POLL_MS;
68 }
69 Ok(())
70}
71
72fn download_to_cache(
75 api: &crate::api::Api,
76 package: &pacenotes_shared::PackageInfo,
77 cache: &mut pacenotes_installer::cache::Cache,
78 progress: crate::http::ProgressFn,
79 cancel: &std::sync::atomic::AtomicBool,
80) -> Result<(), Failure> {
81 std::fs::create_dir_all(cache.root())
82 .map_err(|e| Failure::local("cannot create the download cache", e))?;
83 let temp = cache.root().join(format!(
85 "{}.part",
86 pacenotes_installer::sha256::hex_digest(package.download_path().as_bytes())
87 ));
88
89 let outcome = fetch_verified(api, package, &temp, progress, cancel)
90 .and_then(|()| promote(cache, package, &temp));
91 if outcome.is_err() {
92 let _ = std::fs::remove_file(&temp);
93 }
94 outcome
95}
96
97fn fetch_verified(
98 api: &crate::api::Api,
99 package: &pacenotes_shared::PackageInfo,
100 temp: &std::path::Path,
101 progress: crate::http::ProgressFn,
102 cancel: &std::sync::atomic::AtomicBool,
103) -> Result<(), Failure> {
104 let mut file =
105 std::fs::File::create(temp).map_err(|e| Failure::local("cannot write to the cache", e))?;
106 let mut hasher = pacenotes_installer::sha256::Sha256::new();
107 let mut write_error: Option<std::io::Error> = None;
108
109 let transfer = crate::http::get_streaming(
110 &api.download_url(package),
111 api.token.as_deref(),
112 &mut |chunk| {
113 hasher.update(chunk);
114 std::io::Write::write_all(&mut file, chunk)
115 .map_err(|e| write_error = Some(e))
116 .is_ok()
117 },
118 progress,
119 cancel,
120 );
121 if let Some(e) = write_error {
123 return Err(Failure::local("cannot write to the cache", e));
124 }
125 transfer.map_err(Failure::transfer)?;
126
127 let received = pacenotes_installer::sha256::hex(&hasher.finalize());
128 if received != package.sha256 {
129 return Err(Failure {
131 error: crate::http::HttpError::parse(format!(
132 "{} arrived corrupted: expected sha256 {}, got {received}",
133 package.label(),
134 package.sha256
135 )),
136 retry: true,
137 });
138 }
139 Ok(())
140}
141
142fn promote(
144 cache: &mut pacenotes_installer::cache::Cache,
145 package: &pacenotes_shared::PackageInfo,
146 temp: &std::path::Path,
147) -> Result<(), Failure> {
148 let payload = cache.payload_path(&package.sha256);
149 if payload.exists() {
150 let _ = std::fs::remove_file(temp);
151 } else {
152 std::fs::rename(temp, &payload)
153 .map_err(|e| Failure::local("cannot move the download into the cache", e))?;
154 }
155 cache
156 .insert(pacenotes_installer::tree::CachedPackage::of(package))
157 .map_err(|e| Failure::local("cannot update the cache index", e))
158}