#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
use std::{
collections::HashMap,
time::{Duration, Instant},
};
use reqwest::Client;
use tokio::sync::Mutex;
const CACHE_TTL: Duration = Duration::from_secs(30);
pub const XP_URL: &str = "https://download.nvaccess.org/releases/2017.3/nvda_2017.3.exe";
pub const WIN7_URL: &str = "https://download.nvaccess.org/releases/2023.3.4/nvda_2023.3.4.exe";
#[derive(Debug)]
struct UpdateInfo {
pub launcher_url: Option<String>,
}
impl UpdateInfo {
#[must_use]
fn parse(data: &str) -> Self {
let launcher_url = data.lines().find_map(|line| {
let (key, value) = line.split_once(": ")?;
(key == "launcherUrl").then(|| value.to_owned())
});
Self { launcher_url }
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug)]
pub enum VersionType {
Stable,
Beta,
Alpha,
}
impl VersionType {
const fn as_str(self) -> &'static str {
match self {
Self::Alpha => "snapshot:alpha",
Self::Beta => "beta",
Self::Stable => "stable",
}
}
}
#[derive(Default)]
pub struct NvdaUrl {
client: Client,
cache: Mutex<HashMap<VersionType, (String, Instant)>>,
}
impl NvdaUrl {
pub async fn get_url(&self, version_type: VersionType) -> Option<String> {
let mut cache = self.cache.lock().await;
if let Some((url, timestamp)) = cache.get(&version_type)
&& timestamp.elapsed() < CACHE_TTL {
return Some(url.clone());
}
let url = self.fetch_url(&version_type).await?;
cache.insert(version_type, (url.clone(), Instant::now()));
drop(cache);
Some(url)
}
async fn fetch_url(&self, version_type: &VersionType) -> Option<String> {
let url = format!("https://api.nvaccess.org/nvdaUpdateCheck?versionType={}", version_type.as_str());
let body = self.client.get(&url).send().await.ok()?.text().await.ok()?;
let info = UpdateInfo::parse(&body);
info.launcher_url
}
}