use std::fs;
use std::path::{Path, PathBuf};
use crate::models::common::enums::{Channel, Filetype, Provider};
use crate::models::provider::{Asset, Release};
use crate::models::upstream::Package;
use crate::services::providers::github::{GithubAdapter, GithubClient};
use crate::utils::platform_info::{ArchitectureInfo, CpuArch, format_arch, format_os};
use anyhow::{Result, anyhow};
pub struct ProviderManager {
github: GithubAdapter,
architecture_info: ArchitectureInfo,
}
impl ProviderManager {
pub fn new(github_token: Option<&str>) -> Result<Self> {
let architecture_info = ArchitectureInfo::new();
let github_client = GithubClient::new(github_token);
let github = GithubAdapter::new(github_client?);
Ok(Self {
github,
architecture_info,
})
}
pub async fn get_latest_release(&self, slug: &str, provider: &Provider) -> Result<Release> {
match provider {
Provider::Github => self.github.get_latest_release(slug).await,
}
}
pub async fn get_all_releases(
&self,
slug: &str,
provider: Provider,
per_page: Option<u32>,
) -> Result<Vec<Release>> {
match provider {
Provider::Github => self.github.get_all_releases(slug, per_page).await,
}
}
pub async fn get_release_by_tag(
&self,
slug: &str,
tag: &str,
provider: &Provider,
) -> Result<Release> {
match provider {
Provider::Github => self.github.get_release_by_tag(slug, tag).await,
}
}
pub async fn download_asset<F>(
&self,
asset: &Asset,
provider: &Provider,
cache_path: &Path,
dl_progress: &mut Option<F>,
) -> Result<PathBuf>
where
F: FnMut(u64, u64),
{
let file_name = Path::new(&asset.name)
.file_name()
.ok_or_else(|| anyhow!("Invalid asset name: {}", asset.name))?;
fs::create_dir_all(cache_path)?;
let download_filepath = cache_path.join(file_name);
match provider {
Provider::Github => {
self.github
.download_asset(asset, &download_filepath, dl_progress)
.await?;
}
}
Ok(download_filepath)
}
pub fn find_recommended_asset(&self, release: &Release, package: &Package) -> Result<Asset> {
let target_filetype = if package.filetype == Filetype::Auto {
Self::resolve_auto_filetype(release)?
} else {
package.filetype
};
let compatible_assets: Vec<&Asset> = release
.assets
.iter()
.filter(|a| self.is_potentially_compatible(a))
.filter(|a| a.filetype == target_filetype)
.collect();
compatible_assets
.into_iter()
.max_by_key(|a| self.score_asset(a, package))
.cloned()
.ok_or_else(|| {
anyhow!(
"No compatible assets found for {} on {}",
format_arch(&self.architecture_info.cpu_arch),
format_os(&self.architecture_info.os_kind)
)
})
}
pub fn resolve_auto_filetype(release: &Release) -> Result<Filetype> {
let priority = [
Filetype::AppImage,
Filetype::Archive,
Filetype::Compressed,
Filetype::Binary,
];
priority
.iter()
.find(|&&filetype| {
release.assets.iter().any(|asset| asset.filetype == filetype)
})
.copied()
.ok_or_else(|| anyhow!("No compatible filetype found in release assets"))
}
fn is_valid_update(package: &Package, release: &Release) -> bool {
if package.is_pinned {
return false;
}
let consider_release = match package.channel {
Channel::Stable => !release.is_draft && !release.is_prerelease,
Channel::Beta | Channel::Nightly => !release.is_draft,
Channel::All => true,
};
consider_release && release.version.is_newer_than(&package.version)
}
fn is_potentially_compatible(&self, asset: &Asset) -> bool {
if let Some(target_os) = &asset.target_os
&& *target_os != self.architecture_info.os_kind
{
return false;
}
if let Some(target_arch) = &asset.target_arch {
if *target_arch == self.architecture_info.cpu_arch {
return true;
}
if self.architecture_info.cpu_arch == CpuArch::X86_64 && *target_arch == CpuArch::X86 {
return true;
}
if self.architecture_info.cpu_arch == CpuArch::Aarch64 && *target_arch == CpuArch::Arm {
return true;
}
return *target_arch == self.architecture_info.cpu_arch;
}
true
}
fn score_asset(&self, asset: &Asset, package: &Package) -> i32 {
let name = asset.name.to_lowercase();
let mut score = 0;
if let Some(target_arch) = &asset.target_arch {
if *target_arch == self.architecture_info.cpu_arch {
score += 80;
} else if self.architecture_info.cpu_arch == CpuArch::X86_64
&& *target_arch == CpuArch::X86
{
score += 30;
} else if self.architecture_info.cpu_arch == CpuArch::Aarch64
&& *target_arch == CpuArch::Arm
{
score += 30;
}
}
if asset.filetype == Filetype::Archive {
if name.ends_with(".tar.bz2") || name.ends_with(".tbz") {
score += 15;
} else if name.ends_with(".tar.gz") || name.ends_with(".tgz") {
score += 10;
} else if name.ends_with(".zip") {
score += 5;
}
}
if asset.filetype == Filetype::Compressed {
if name.ends_with(".bz2") {
score += 10;
} else if name.ends_with(".gz") {
score += 5;
}
}
if asset.filetype == Filetype::Binary {
if Path::new(&name).extension().is_none() {
score += 10;
}
}
if name.contains("static") {
score += 5;
}
if name.contains("debug") || name.contains("symbols") {
score -= 20;
}
if !name.contains(&package.name.to_lowercase()) {
score -= 40;
}
if asset.size < 100_000 || asset.size > 500_000_000 {
score -= 20;
}
if let Some(asset_hint) = &package.pattern {
if name.contains(asset_hint) {
score += 50;
}
}
score
}
}