#![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 XP_HASH: &str = "386e7acb8cc3ecaabc8005894cf783b51a8ac7f6";
pub const WIN7_URL: &str = "https://download.nvaccess.org/releases/2023.3.4/nvda_2023.3.4.exe";
pub const WIN7_HASH: &str = "985a6deab01edb55fbedc9b056956e30120db290";
#[derive(Debug)]
struct UpdateInfo {
pub launcher_url: Option<String>,
pub launcher_hash: Option<String>,
}
impl UpdateInfo {
#[must_use]
fn parse(data: &str) -> Self {
let mut launcher_url = None;
let mut launcher_hash = None;
for line in data.lines() {
let Some((key, value)) = line.split_once(": ") else { continue };
match key {
"launcherUrl" => launcher_url = Some(value.to_owned()),
"launcherHash" => launcher_hash = Some(value.to_owned()),
_ => (),
}
}
Self { launcher_url, launcher_hash }
}
}
#[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, String, Instant)>>,
}
impl NvdaUrl {
pub async fn get_url(&self, version_type: VersionType) -> Option<String> {
Some(self.get_details(version_type).await?.0)
}
pub async fn get_details(&self, version_type: VersionType) -> Option<(String, String)> {
let mut cache = self.cache.lock().await;
if let Some((url, sha1_hash, timestamp)) = cache.get(&version_type)
&& timestamp.elapsed() < CACHE_TTL
{
return Some((url.clone(), sha1_hash.clone()));
}
let (url, sha1_hash) = self.fetch_url(&version_type).await?;
cache.insert(version_type, (url.clone(), sha1_hash.clone(), Instant::now()));
drop(cache);
Some((url, sha1_hash))
}
async fn fetch_url(&self, version_type: &VersionType) -> Option<(String, 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);
Some((info.launcher_url?, info.launcher_hash?))
}
}