use anyhow::anyhow;
use anyhow::Result;
use chrono::DateTime;
use chrono::Utc;
use regex::Regex;
use semver::Version;
use serde::Deserialize;
use serde::Serialize;
use tokio::fs;
use crate::packages::Package;
use crate::packages::PackageType;
use crate::services::github::api;
#[derive(Clone, PartialEq, Debug)]
pub struct LocalVersion {
pub file_name: String,
pub file_format: String,
pub path: String,
pub semver: Option<Version>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct RemoteVersion {
pub name: String,
pub tag_name: String,
pub prerelease: bool,
}
#[derive(Debug, Clone)]
pub enum VersionStatus {
Installed,
Used,
NotInstalled,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UpstreamVersion {
pub tag_name: String,
pub target_commitish: Option<String>,
pub published_at: DateTime<Utc>,
}
#[derive(Debug, Clone)]
pub struct ParsedVersion {
pub tag_name: String,
pub version_type: VersionType,
pub non_parsed_string: String,
pub semver: Option<Version>,
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum VersionType {
Normal,
Latest,
}
impl VersionType {
pub fn from_string(version: &str) -> VersionType {
match version {
"latest" => VersionType::Latest,
_ => VersionType::Normal,
}
}
pub async fn parse(
version: &str,
client: Option<&reqwest::Client>,
package_type: PackageType,
) -> Result<ParsedVersion> {
let version_type = VersionType::from_string(version);
match version_type {
VersionType::Normal => Ok(parse_normal_version(version, version_type).await?),
VersionType::Latest => Ok(fetch_latest_version(client, package_type).await?),
}
}
}
pub async fn parse_normal_version(
version: &str,
version_type: VersionType,
) -> Result<ParsedVersion> {
let semver = semver(version)?;
let returned_version = match (semver, version.starts_with('v')) {
(true, false) => parse_semver(version)?,
_ => ParsedVersion {
tag_name: version.to_string(),
version_type,
non_parsed_string: version.to_string(),
semver: None,
},
};
Ok(returned_version)
}
pub async fn fetch_latest_version(
client: Option<&reqwest::Client>,
package_type: PackageType,
) -> Result<ParsedVersion> {
let url = package_type.get_latest_url();
let response = api(client, url).await.unwrap();
let latest_version: UpstreamVersion = serde_json::from_str(&response)?;
let tag_name = latest_version.tag_name.clone();
parse_normal_version(&tag_name, VersionType::Latest).await
}
pub fn semver(version: &str) -> Result<bool> {
Ok(Regex::new(r"^v?[0-9]+\.[0-9]+\.[0-9]+$")?.is_match(version))
}
fn parse_semver(version: &str) -> Result<ParsedVersion> {
let version = version.to_string();
let semver = Version::parse(&version)?;
Ok(ParsedVersion {
tag_name: version.clone(),
version_type: VersionType::Normal,
non_parsed_string: version.clone(),
semver: Some(semver),
})
}
pub async fn is_version_installed(version: &str, package: Package) -> Result<bool> {
let downloads_dir = crate::fs::get_downloads_directory(package).await?;
let mut dir = tokio::fs::read_dir(&downloads_dir).await?;
while let Some(directory) = dir.next_entry().await? {
let name = directory.file_name().to_str().unwrap().to_owned();
if !version.eq(&name) {
continue;
} else {
return Ok(true);
}
}
Ok(false)
}
pub async fn get_current_version(package: Package) -> Result<String> {
let mut downloads_dir = crate::fs::get_downloads_directory(package).await?;
downloads_dir.push("used");
tokio::fs::read_to_string(&downloads_dir)
.await
.map_err(|_| anyhow!("Could not read the current version"))
}
pub async fn is_version_used(version: &str, package: Package) -> bool {
let current_version = get_current_version(package).await;
match current_version {
Ok(current_version) => current_version.eq(version),
Err(_) => false,
}
}
pub async fn switch_version(version: &ParsedVersion, package: Package) -> Result<()> {
std::env::set_current_dir(crate::fs::get_downloads_directory(package).await?)?;
let file_version: String = version.tag_name.to_string();
fs::write("used", &file_version).await?;
Ok(())
}