rido 0.5.4

Fetch valid URLs and checksums of Microsoft Operating Systems.
Documentation
use crate::{RidoError, ValidateLanguage, ValidateWithArch, WindowsArchitecture, WindowsLanguage, WindowsRelease};
use regex::Regex;
use std::fmt;
use strum_macros::EnumIter;

pub fn get_enterprise_info(release: EnterpriseRelease, lang: EnterpriseLanguage, arch: WindowsArchitecture) -> Result<(String, Option<String>), RidoError> {
    if !lang.validate(release.into()) {
        return Err(RidoError::InvalidLanguage(release.into(), lang.into()));
    }
    let valid_release = match release {
        EnterpriseRelease::TenEnterprise | EnterpriseRelease::TenLtsc => "windows-10-enterprise",
        EnterpriseRelease::ElevenEnterprise => "windows-11-enterprise",
        EnterpriseRelease::Server2012R2 => "windows-server-2012-r2",
        EnterpriseRelease::Server2016 => "windows-server-2016",
        EnterpriseRelease::Server2019 => "windows-server-2019",
        EnterpriseRelease::Server2022 => "windows-server-2022",
    };

    let url = format!("https://www.microsoft.com/en-us/evalcenter/download-{valid_release}");

    let client = reqwest::blocking::Client::new();
    let download_page_html = client.get(url).send()?.text()?;

    if download_page_html.is_empty() {
        return Err(RidoError::EmptyResponse);
    }

    let (culture, country) = match lang {
        EnterpriseLanguage::BrazilianPortuguese => ("pt-br", "BR"),
        EnterpriseLanguage::EnglishGB => ("en-gb", "GB"),
        EnterpriseLanguage::EnglishUS => ("en-us", "US"),
        EnterpriseLanguage::French => ("fr-fr", "FR"),
        EnterpriseLanguage::German => ("de-de", "DE"),
        EnterpriseLanguage::Italian => ("it-it", "IT"),
        EnterpriseLanguage::Japanese => ("ja-jp", "JP"),
        EnterpriseLanguage::Korean => ("ko-kr", "KR"),
        EnterpriseLanguage::Russian => ("ru-ru", "RU"),
        EnterpriseLanguage::SimplifiedChinese => ("zh-cn", "CN"),
        EnterpriseLanguage::Spanish => ("es-es", "ES"),
        EnterpriseLanguage::TraditionalChinese => ("zh-tw", "TW"),
    };

    let bits = match arch {
        WindowsArchitecture::i686 => "32",
        WindowsArchitecture::x86_64 => "64",
    };

    let iso_regex = Regex::new(r#"href="(https://go\.microsoft\.com/fwlink/p/\?LinkID=\d{7}&clcid=0x\w{3}&culture=([a-z]{2}-[a-z]{2})&country=(\w{2}))">\s(64|32)-bit"#).unwrap();

    let urls = iso_regex
        .captures_iter(&download_page_html)
        .filter(|c| c[2] == *culture && c[3] == *country && c[4] == *bits)
        .collect::<Vec<_>>();

    let url_capture = match release {
        EnterpriseRelease::TenLtsc => urls.get(1),
        _ => urls.first(),
    }
    .ok_or(RidoError::HTMLParse)?;

    let url = url_capture[1].to_string();

    Ok((url, None))
}

#[derive(PartialEq, EnumIter, Debug, Copy, Clone)]
pub enum EnterpriseRelease {
    ElevenEnterprise,
    TenEnterprise,
    TenLtsc,
    Server2022,
    Server2019,
    Server2016,
    Server2012R2,
}

impl From<EnterpriseRelease> for WindowsRelease {
    fn from(release: EnterpriseRelease) -> Self {
        Self::Enterprise(release)
    }
}

impl fmt::Display for EnterpriseRelease {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let text = match self {
            EnterpriseRelease::TenEnterprise => "Windows 10 Enterprise",
            EnterpriseRelease::TenLtsc => "Windows 10 LTSC",
            EnterpriseRelease::ElevenEnterprise => "Windows 11 Enterprise",
            EnterpriseRelease::Server2012R2 => "Windows Server 2012 R2",
            EnterpriseRelease::Server2016 => "Windows Server 2016",
            EnterpriseRelease::Server2019 => "Windows Server 2019",
            EnterpriseRelease::Server2022 => "Windows Server 2022",
        };
        write!(f, "{text}")
    }
}

impl TryFrom<&str> for EnterpriseRelease {
    type Error = RidoError;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Ok(match value {
            "10-enterprise" => Self::TenEnterprise,
            "10-ltsc" => Self::TenLtsc,
            "11-enterprise" => Self::ElevenEnterprise,
            "server-2012-r2" => Self::Server2012R2,
            "server-2016" => Self::Server2016,
            "server-2019" => Self::Server2019,
            "server-2022" => Self::Server2022,
            _ => return Err(RidoError::InvalidReleaseStr),
        })
    }
}

#[derive(PartialEq, EnumIter, Debug, Copy, Clone)]
pub enum EnterpriseLanguage {
    BrazilianPortuguese,
    EnglishUS,
    EnglishGB,
    French,
    German,
    Italian,
    Japanese,
    Korean,
    Russian,
    SimplifiedChinese,
    Spanish,
    TraditionalChinese,
}

impl From<EnterpriseLanguage> for WindowsLanguage {
    fn from(lang: EnterpriseLanguage) -> Self {
        Self::Enterprise(lang)
    }
}

impl fmt::Display for EnterpriseLanguage {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let text = match self {
            Self::BrazilianPortuguese => "Portuguese (Brazil)",
            Self::EnglishUS => "English (United States)",
            Self::EnglishGB => "English (Great Britain)",
            Self::French => "French",
            Self::German => "German",
            Self::Italian => "Italian",
            Self::Japanese => "Japanese",
            Self::Korean => "Korean",
            Self::Russian => "Russian",
            Self::SimplifiedChinese => "Chinese (Simplified)",
            Self::Spanish => "Spanish",
            Self::TraditionalChinese => "Chinese (Traditional)",
        };
        write!(f, "{text}")
    }
}

impl TryFrom<&str> for EnterpriseLanguage {
    type Error = RidoError;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Ok(match value {
            "Portuguese (Brazil)" => Self::BrazilianPortuguese,
            "English (United States)" => Self::EnglishUS,
            "English (Great Britain)" => Self::EnglishGB,
            "French" => Self::French,
            "German" => Self::German,
            "Italian" => Self::Italian,
            "Japanese" => Self::Japanese,
            "Korean" => Self::Korean,
            "Russian" => Self::Russian,
            "Chinese (Simplified)" => Self::SimplifiedChinese,
            "Spanish" => Self::Spanish,
            "Chinese (Traditional)" => Self::TraditionalChinese,
            _ => return Err(RidoError::InvalidLanguageStr),
        })
    }
}

impl ValidateLanguage for EnterpriseLanguage {
    fn validate(&self, release: WindowsRelease) -> bool {
        match release {
            WindowsRelease::Enterprise(release) => !match release {
                EnterpriseRelease::TenLtsc | EnterpriseRelease::TenEnterprise => matches!(self, EnterpriseLanguage::Russian),
                _ => matches!(
                    self,
                    EnterpriseLanguage::BrazilianPortuguese | EnterpriseLanguage::EnglishGB | EnterpriseLanguage::Korean | EnterpriseLanguage::TraditionalChinese
                ),
            },
            #[allow(unreachable_patterns)]
            _ => false,
        }
    }
}

impl ValidateWithArch for EnterpriseRelease {
    fn validate(&self, arch: WindowsArchitecture) -> bool {
        if let EnterpriseRelease::TenLtsc | EnterpriseRelease::TenEnterprise = self {
            true
        } else {
            arch == WindowsArchitecture::x86_64
        }
    }
}