summaryrefslogtreecommitdiffstats
path: root/src/launcher/download.rs
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-02-01 23:06:37 -0600
committerLibravatar bigfoot547 <[email protected]>2025-02-01 23:06:37 -0600
commitc19a1077e85334a3e5ba885a60b03d76409a2b2e (patch)
tree5e726e8180770ac9c2f6c415a0437d6d2c29c226 /src/launcher/download.rs
parentrandom changes (diff)
restructure project
Diffstat (limited to 'src/launcher/download.rs')
-rw-r--r--src/launcher/download.rs267
1 files changed, 0 insertions, 267 deletions
diff --git a/src/launcher/download.rs b/src/launcher/download.rs
deleted file mode 100644
index 132cd7f..0000000
--- a/src/launcher/download.rs
+++ /dev/null
@@ -1,267 +0,0 @@
-use std::error::Error;
-use std::fmt::{Debug, Display, Formatter};
-use std::path::{Path, PathBuf};
-use futures::{stream, StreamExt, TryStream, TryStreamExt};
-use log::debug;
-use reqwest::{Client, Method, RequestBuilder};
-use sha1_smol::{Digest, Sha1};
-use tokio::fs;
-use tokio::fs::File;
-use tokio::io::{self, AsyncWriteExt};
-use crate::util;
-use crate::util::{FileVerifyError, IntegrityError, USER_AGENT};
-
-pub trait Download: Debug + Display {
- // return Ok(None) to skip downloading this file
- async fn prepare(&mut self, client: &Client) -> Result<Option<RequestBuilder>, Box<dyn Error>>;
- async fn handle_chunk(&mut self, chunk: &[u8]) -> Result<(), Box<dyn Error>>;
- async fn finish(&mut self) -> Result<(), Box<dyn Error>>;
-}
-
-pub trait FileDownload: Download {
- fn get_path(&self) -> &Path;
-}
-
-pub struct MultiDownloader<'j, T: Download + 'j, I: Iterator<Item = &'j mut T>> {
- jobs: I,
- nconcurrent: usize
-}
-
-#[derive(Debug, Clone, Copy)]
-pub enum Phase {
- Prepare,
- Send,
- Receive,
- HandleChunk,
- Finish
-}
-
-impl Display for Phase {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- match self {
- /* an error occurred while (present participle) ... */
- Self::Prepare => f.write_str("preparing the request"),
- Self::Send => f.write_str("sending the request"),
- Self::Receive => f.write_str("receiving response data"),
- Self::HandleChunk => f.write_str("handling response data"),
- Self::Finish => f.write_str("finishing the request"),
- }
- }
-}
-
-pub struct PhaseDownloadError<'j, T: Download> {
- phase: Phase,
- inner: Box<dyn Error>,
- job: &'j T
-}
-
-impl<T: Download> Debug for PhaseDownloadError<'_, T> {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("PhaseDownloadError")
- .field("phase", &self.phase)
- .field("inner", &self.inner)
- .field("job", &self.job)
- .finish()
- }
-}
-
-impl<T: Download> Display for PhaseDownloadError<'_, T> {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- write!(f, "error while {} ({}): {}", self.phase, self.job, self.inner)
- }
-}
-
-impl<T: Download> Error for PhaseDownloadError<'_, T> {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- Some(&*self.inner)
- }
-}
-
-impl<'j, T: Download> PhaseDownloadError<'j, T> {
- fn new(phase: Phase, inner: Box<dyn Error>, job: &'j T) -> Self {
- PhaseDownloadError {
- phase, inner, job
- }
- }
-}
-
-impl<'j, T: Download + 'j, I: Iterator<Item = &'j mut T>> MultiDownloader<'j, T, I> {
- pub fn new(jobs: I) -> MultiDownloader<'j, T, I> {
- Self::with_concurrent(jobs, 24)
- }
-
- pub fn with_concurrent(jobs: I, n: usize) -> MultiDownloader<'j, T, I> {
- assert!(n > 0);
-
- MultiDownloader {
- jobs,
- nconcurrent: n
- }
- }
-
- pub async fn perform(self, client: &'j Client) -> impl TryStream<Ok = (), Error = PhaseDownloadError<'j, T>> {
- stream::iter(self.jobs).map(move |job| Ok(async move {
- macro_rules! map_err {
- ($result:expr, $phase:expr, $job:expr) => {
- match $result {
- Ok(v) => v,
- Err(e) => return Err(PhaseDownloadError::new($phase, e.into(), $job))
- }
- }
- }
-
- let Some(rq) = map_err!(job.prepare(client).await, Phase::Prepare, job) else {
- return Ok(())
- };
-
- let rq = rq.header(reqwest::header::USER_AGENT, USER_AGENT);
-
- let mut data = map_err!(map_err!(rq.send().await, Phase::Send, job).error_for_status(), Phase::Send, job).bytes_stream();
-
- while let Some(bytes) = data.next().await {
- let bytes = map_err!(bytes, Phase::Receive, job);
-
- map_err!(job.handle_chunk(bytes.as_ref()).await, Phase::HandleChunk, job);
- }
-
- job.finish().await.map_err(|e| PhaseDownloadError::new(Phase::Finish, e, job))?;
-
- Ok(())
- })).try_buffer_unordered(self.nconcurrent)
- }
-}
-
-pub struct VerifiedDownload {
- url: String,
- expect_size: Option<usize>,
- expect_sha1: Option<Digest>,
-
- path: PathBuf,
- file: Option<File>,
- sha1: Sha1,
- tally: usize
-}
-
-impl Debug for VerifiedDownload {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("VerifiedDownload")
- .field("url", &self.url)
- .field("expect_size", &self.expect_size)
- .field("expect_sha1", &self.expect_sha1)
- .field("path", &self.path).finish()
- }
-}
-
-impl Display for VerifiedDownload {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- write!(f, "downloading {} to {}", self.url, self.path.display())
- }
-}
-
-impl VerifiedDownload {
- pub fn new(url: &str, path: &Path, expect_size: Option<usize>, expect_sha1: Option<Digest>) -> VerifiedDownload {
- VerifiedDownload {
- url: url.to_owned(),
- path: path.to_owned(),
-
- expect_size,
- expect_sha1,
-
- file: None,
- sha1: Sha1::new(),
- tally: 0
- }
- }
-
- pub fn with_size(mut self, expect: usize) -> VerifiedDownload {
- self.expect_size = Some(expect);
- self
- }
-
- pub fn with_sha1(mut self, expect: Digest) -> VerifiedDownload {
- self.expect_sha1.replace(expect);
- self
- }
-
- pub fn get_url(&self) -> &str {
- &self.url
- }
-
- pub fn get_expect_size(&self) -> Option<usize> {
- self.expect_size
- }
-
- pub fn get_expect_sha1(&self) -> Option<Digest> {
- self.expect_sha1
- }
-
- pub async fn make_dirs(&self) -> Result<(), io::Error> {
- fs::create_dir_all(self.path.parent().expect("download created with no containing directory (?)")).await
- }
-
- async fn open_output(&mut self) -> Result<(), io::Error> {
- self.file.replace(File::create(&self.path).await?);
- Ok(())
- }
-}
-
-impl Download for VerifiedDownload {
- async fn prepare(&mut self, client: &Client) -> Result<Option<RequestBuilder>, Box<dyn Error>> {
- if !util::should_download(&self.path, self.expect_size, self.expect_sha1).await? {
- return Ok(None)
- }
-
- // potentially racy to close the file and reopen it... :/
- self.open_output().await?;
-
- Ok(Some(client.request(Method::GET, &self.url)))
- }
-
- async fn handle_chunk(&mut self, chunk: &[u8]) -> Result<(), Box<dyn Error>> {
- self.file.as_mut().unwrap().write_all(chunk).await?;
- self.tally += chunk.len();
- self.sha1.update(chunk);
-
- Ok(())
- }
-
- async fn finish(&mut self) -> Result<(), Box<dyn Error>> {
- let digest = self.sha1.digest();
-
- if let Some(d) = self.expect_sha1 {
- if d != digest {
- debug!("Could not download {}: sha1 mismatch (exp {}, got {}).", self.path.display(), d, digest);
- return Err(IntegrityError::Sha1Mismatch { expect: d, actual: digest }.into());
- }
- } else if let Some(s) = self.expect_size {
- if s != self.tally {
- debug!("Could not download {}: size mismatch (exp {}, got {}).", self.path.display(), s, self.tally);
- return Err(IntegrityError::SizeMismatch { expect: s, actual: self.tally }.into());
- }
- }
-
- debug!("Successfully downloaded {} ({} bytes).", self.path.display(), self.tally);
-
- // release the file descriptor (don't want to wait until it's dropped automatically because idk when that would be)
- drop(self.file.take().unwrap());
-
- Ok(())
- }
-}
-
-impl FileDownload for VerifiedDownload {
- fn get_path(&self) -> &Path {
- &self.path
- }
-}
-
-pub async fn verify_files(files: impl Iterator<Item = &mut VerifiedDownload>) -> Result<(), FileVerifyError> {
- stream::iter(files)
- .map(|dl| Ok(async move {
- debug!("Verifying library {}", dl.get_path().display());
- util::verify_file(dl.get_path(), dl.get_expect_size(), dl.get_expect_sha1()).await
- }))
- .try_buffer_unordered(32)
- .try_fold((), |_, _| async {Ok(())})
- .await
-}