pub mod archive;
pub mod events;
pub mod fetch;
pub mod install;
pub mod resolve;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::mpsc::UnboundedSender;
use crate::downloader::events::{DownloaderError, ProgressEvent, UnsupportedReason};
pub struct InstallSpec {
pub name: String,
pub version: String,
pub repo_url: Option<String>,
pub bins: Vec<String>,
}
pub struct InstallOutcome {
pub name: String,
pub old_version: Option<String>,
pub new_version: String,
}
pub async fn download_and_install(
client: &reqwest::Client,
spec: InstallSpec,
old_version: Option<String>,
events: UnboundedSender<ProgressEvent>,
cancel: Arc<AtomicBool>,
) -> Result<InstallOutcome, DownloaderError> {
let _ = events.send(ProgressEvent::Resolving {
name: spec.name.clone(),
});
if cancel.load(Ordering::SeqCst) {
return Err(DownloaderError::Cancelled);
}
let targets = resolve::current_targets();
if targets.is_empty() {
return Err(DownloaderError::Unsupported(UnsupportedReason::UnsupportedPlatform));
}
let repo_url = spec
.repo_url
.as_deref()
.ok_or(DownloaderError::Unsupported(UnsupportedReason::NoMetadataAndNoConvention))?;
let mut name_candidates: Vec<String> = vec![spec.name.clone()];
for b in &spec.bins {
if !name_candidates.contains(b) {
name_candidates.push(b.clone());
}
}
let candidates = resolve::candidate_urls(&name_candidates, &spec.version, repo_url, &targets)?;
let fetched = fetch::fetch(client, &spec.name, &candidates, &events, cancel.clone()).await?;
if cancel.load(Ordering::SeqCst) {
return Err(DownloaderError::Cancelled);
}
let _ = events.send(ProgressEvent::Extracting {
name: spec.name.clone(),
});
let fmt = if fetched.winning_url.ends_with(".zip") {
resolve::ArchiveFmt::Zip
} else {
resolve::ArchiveFmt::TarGz
};
let mut bin_candidates: Vec<String> = spec.bins.clone();
if bin_candidates.is_empty() {
bin_candidates.push(spec.name.clone());
}
let extracted = archive::extract(&fetched.archive_path, fmt, &bin_candidates)?;
if cancel.load(Ordering::SeqCst) {
return Err(DownloaderError::Cancelled);
}
let _ = events.send(ProgressEvent::Installing {
name: spec.name.clone(),
});
let _installed_path = install::install_binary(
&extracted.binary_path,
&extracted.binary_name,
&spec.version,
)?;
let _ = events.send(ProgressEvent::Done {
name: spec.name.clone(),
version: spec.version.clone(),
});
Ok(InstallOutcome {
name: spec.name,
old_version,
new_version: spec.version,
})
}