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 })).ok(); // suppress this result since we don't really care if the caller hung up 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") } } }