any_version_manager/
lib.rs

1use serde::{Deserialize, Serialize};
2use smol_str::SmolStr;
3use std::future::Future;
4use std::pin::Pin;
5use std::task::{Context, Poll};
6use std::{path::PathBuf, sync::atomic::AtomicBool};
7
8pub mod io;
9pub mod platform;
10pub mod tool;
11
12#[derive(Debug, Deserialize)]
13pub struct UrlMirrorEntry {
14    from: String,
15    to: String,
16}
17#[derive(Debug, Default, Deserialize)]
18pub struct UrlMirror {
19    mirror: Vec<UrlMirrorEntry>,
20}
21
22#[derive(Debug, Default, Deserialize)]
23pub struct Config {
24    #[serde(flatten)]
25    pub mirror: Option<UrlMirror>,
26    pub data_path: Option<PathBuf>,
27    pub rustup: Option<RustupConfig>,
28}
29
30#[derive(Debug, Default, Deserialize)]
31pub struct RustupConfig {
32    pub path: Option<PathBuf>,
33}
34
35pub async fn spawn_blocking<T: Send + 'static>(
36    f: impl FnOnce() -> anyhow::Result<T> + Send + 'static,
37) -> anyhow::Result<T> {
38    match tokio::task::spawn_blocking(f).await {
39        Ok(r) => r,
40        Err(_) => Err(anyhow::anyhow!("Failed to join spawned IO task")),
41    }
42}
43
44pub struct HttpClient {
45    mirror: UrlMirror,
46    client_inner: reqwest::Client,
47}
48
49impl HttpClient {
50    pub fn new(mirror: UrlMirror) -> HttpClient {
51        HttpClient {
52            mirror,
53            client_inner: reqwest::Client::new(),
54        }
55    }
56
57    pub fn get(&self, url: &str) -> reqwest::RequestBuilder {
58        for entry in &self.mirror.mirror {
59            if let Some(rest) = url.strip_prefix(&entry.from) {
60                let mut result = String::new();
61                result.push_str(entry.to.as_str());
62                result.push_str(rest);
63                log::debug!("Applied mirror {} => {}", url, result);
64                return self.client_inner.get(result);
65            }
66        }
67
68        self.client_inner.get(url)
69    }
70}
71
72pub enum Status {
73    InProgress {
74        name: SmolStr,
75        progress_ratio: Option<(u64, u64)>,
76    },
77    Stopped,
78}
79
80#[derive(Clone, Default, Deserialize, Serialize)]
81pub struct FileHash {
82    #[serde(skip_serializing_if = "Option::is_none")]
83    sha1: Option<SmolStr>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    sha256: Option<SmolStr>,
86}
87
88static CANCELLED: AtomicBool = AtomicBool::new(false);
89
90pub fn set_cancelled() {
91    CANCELLED.store(true, std::sync::atomic::Ordering::Relaxed);
92}
93
94pub fn is_cancelled() -> bool {
95    CANCELLED.load(std::sync::atomic::Ordering::Relaxed)
96}
97
98pub struct CancellableFuture<Fut> {
99    inner: Fut,
100}
101
102impl<Fut> CancellableFuture<Fut> {
103    pub fn new(inner: Fut) -> Self {
104        CancellableFuture { inner }
105    }
106}
107
108impl<Fut> Future for CancellableFuture<Fut>
109where
110    Fut: Future,
111{
112    type Output = Option<Fut::Output>;
113
114    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
115        if is_cancelled() {
116            Poll::Ready(None)
117        } else {
118            // TODO: is unsafe right?
119            let inner = unsafe { self.map_unchecked_mut(|s| &mut s.inner) };
120            match inner.poll(cx) {
121                Poll::Ready(output) => Poll::Ready(Some(output)),
122                Poll::Pending => Poll::Pending,
123            }
124        }
125    }
126}