From 5d68d164d9a7bff8f3015257f25eb71c44829ddf Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Tue, 7 Jan 2025 03:43:43 -0600 Subject: untested moment (remove reqwest) --- src/launcher/constants.rs | 7 +++ src/launcher/download.rs | 36 ++++++++++++ src/launcher/request.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++++ src/launcher/version.rs | 5 +- 4 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 src/launcher/download.rs create mode 100644 src/launcher/request.rs (limited to 'src/launcher') diff --git a/src/launcher/constants.rs b/src/launcher/constants.rs index 8a9bd1a..698081b 100644 --- a/src/launcher/constants.rs +++ b/src/launcher/constants.rs @@ -1 +1,8 @@ +use const_format::formatcp; + +const PKG_NAME: &str = env!("CARGO_PKG_NAME"); +const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); +const CRATE_NAME: &str = env!("CARGO_CRATE_NAME"); + +pub const USER_AGENT: &str = formatcp!("{PKG_NAME}/{PKG_VERSION} (in {CRATE_NAME})"); pub const URL_VERSION_MANIFEST: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; diff --git a/src/launcher/download.rs b/src/launcher/download.rs new file mode 100644 index 0000000..4294d33 --- /dev/null +++ b/src/launcher/download.rs @@ -0,0 +1,36 @@ +use std::path::{Path, PathBuf}; +use sha1_smol::Digest; + +pub trait Download { + fn get_url(&self) -> &str; + fn get_path(&self) -> &Path; + fn get_expect_digest(&self) -> Option; + fn get_expect_size(&self) -> Option; + + fn always_redownload(&self) -> bool; +} + +pub type DownloadJob = dyn Download + Sync + Send; + +pub struct MultiDownloader<'j, 'js> { + jobs: &'js [&'j DownloadJob], + nhandles: usize +} + +impl<'j, 'js> MultiDownloader<'j, 'js> { + pub fn new(jobs: &'js [&'j DownloadJob]) -> MultiDownloader<'j, 'js> { + Self::with_handles(jobs, 8) + } + + pub fn with_handles(jobs: &'js [&'j DownloadJob], nhandles: usize) -> MultiDownloader<'j, 'js> { + assert!(nhandles > 0); + + MultiDownloader { + jobs, nhandles + } + } + + fn do_it(&self) { + + } +} \ No newline at end of file diff --git a/src/launcher/request.rs b/src/launcher/request.rs new file mode 100644 index 0000000..df89a8b --- /dev/null +++ b/src/launcher/request.rs @@ -0,0 +1,139 @@ +use std::error::Error; +use std::fmt::Display; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use curl::easy::{Easy}; +use tokio::sync::oneshot; +use tokio::sync::oneshot::Receiver; +use tokio::task; +use crate::launcher::constants::USER_AGENT; + +// yeah this is basically reqwest but bad (I did not want to rely on both reqwest and curl) + +#[derive(Clone, Copy)] +enum FetchState { + Primed, + Running, + Complete +} + +pub struct EasyFetch { + easy: Option, + state: FetchState, + response: Option>> +} + +impl EasyFetch { + fn new(easy: Easy) -> Self { + EasyFetch { + easy: Some(easy), + state: FetchState::Primed, + response: None + } + } + + pub fn get(url: &str) -> Self { + let mut easy = Easy::new(); + easy.useragent(USER_AGENT).expect("couldn't set user agent"); + easy.get(true).expect("couldn't set request method"); + easy.url(url).expect("couldn't set url"); + + Self::new(easy) + } +} + +#[derive(Debug)] +pub struct FetchResult { + easy: Easy, + response_code: u32, + data: Vec, +} + +#[derive(Debug)] +pub struct FetchResponseError(u32); + +impl FetchResponseError { + pub fn get_code(&self) -> u32 { + self.0 + } +} + +impl Display for FetchResponseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "http response: {}", self.0) + } +} + +impl Error for FetchResponseError {} + +impl FetchResult { + pub fn get_response_code(&self) -> u32 { + self.response_code + } + + pub fn get_data(&self) -> &[u8] { + &self.data + } + + pub fn get_data_string(&self) -> String { + String::from_utf8_lossy(&self.data).to_string() + } + + pub fn error_for_status(self) -> Result { + if self.response_code / 100 == 2 { + Ok(self) + } else { + Err(FetchResponseError(self.response_code)) + } + } +} + +impl Future for EasyFetch { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let self_ref = self.get_mut(); + + match &self_ref.state { + FetchState::Primed => { + self_ref.state = FetchState::Running; + let mut easy = self_ref.easy.take().unwrap(); + let waker = cx.waker().clone(); + + let (tx, rx) = oneshot::channel::>(); + self_ref.response.replace(rx); + + task::spawn_blocking(move || { + let mut out_data: Vec = Vec::new(); + let mut transfer = easy.transfer(); + + transfer.write_function(|data| { + out_data.extend_from_slice(data); + Ok(data.len()) + }).expect("infallible curl operation failed"); + + let res = transfer.perform(); + drop(transfer); // have to explicitly drop to release borrow on "easy" + + out_data.shrink_to_fit(); + + tx.send(res.map(|_| FetchResult { + response_code: easy.response_code().expect("querying response code should not fail"), + data: out_data, + easy + })).expect("curl fetch reader hangup (this shouldn't happen)"); + waker.wake(); + }); + + Poll::Pending + }, + FetchState::Running => { + self_ref.state = FetchState::Complete; + Poll::Ready(self_ref.response.take().unwrap().try_recv() + .expect("curl fetch writer hangup or not ready (this shouldn't happen)")) + }, + FetchState::Complete => panic!("fetch polled after completion") + } + } +} \ No newline at end of file diff --git a/src/launcher/version.rs b/src/launcher/version.rs index f4cdd6c..0337864 100644 --- a/src/launcher/version.rs +++ b/src/launcher/version.rs @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf}; use log::{debug, info, warn}; use sha1_smol::Digest; +use super::request::EasyFetch; use crate::util; use crate::version::{*, manifest::*}; @@ -18,7 +19,7 @@ struct RemoteVersionList { impl RemoteVersionList { async fn new() -> Result> { - let text = reqwest::get(URL_VERSION_MANIFEST).await?.error_for_status()?.text().await?; + let text = EasyFetch::get(URL_VERSION_MANIFEST).await?.error_for_status()?.get_data_string(); let manifest: VersionManifest = serde_json::from_str(text.as_str())?; let mut versions = HashMap::new(); @@ -45,7 +46,7 @@ impl RemoteVersionList { } // download it - let ver_text = reqwest::get(ver.url.as_str()).await?.error_for_status()?.text().await?; + let ver_text = EasyFetch::get(ver.url.as_str()).await?.error_for_status()?.get_data_string(); // make sure it's valid util::verify_sha1(ver.sha1, ver_text.as_str()) -- cgit v1.2.3-70-g09d2