1use std::env::consts::{ARCH, OS};
2use std::fs::File;
3use std::io::{BufReader, copy};
4use std::path::PathBuf;
5
6pub fn install(
7 provider: &str,
8 user: &str,
9 repo: &str,
10) -> Result<PathBuf, Box<dyn std::error::Error>> {
11 let current_os = match OS {
12 "windows" => "windows",
13 "macos" => "darwin",
14 "linux" => "linux",
15 other => other,
16 };
17
18 let current_arch = match ARCH {
19 "x86_64" => "amd64",
20 "aarch64" => "arm64",
21 other => other,
22 };
23
24 let extension = if OS == "windows" { "zip" } else { "tar.gz" };
25
26 let url = format!(
27 "https://{provider}/{user}/{repo}/releases/latest/download/{repo}-{current_os}-{current_arch}.{extension}"
28 );
29
30 println!("Detected Target: OS={}, ARCH={}", OS, ARCH);
31 println!("Downloading release from: {}", url);
32
33 let client = reqwest::blocking::Client::new();
34 let response = client.get(&url).send()?;
35
36 if !response.status().is_success() {
37 return Err(format!("Server returned an error status: {}", response.status()).into());
38 }
39
40 let target_dir = tempfile::tempdir()?.keep();
41 let target_filename = format!("{repo}-{current_os}-{current_arch}.{extension}");
42 let archive_path = target_dir.join(&target_filename);
43
44 let mut archive_file = File::create(&archive_path)?;
45 let mut content = response;
46 let bytes = copy(&mut content, &mut archive_file)?;
47 println!(
48 "Successfully downloaded {} bytes to {:?}",
49 bytes, archive_path
50 );
51
52 let file = File::open(&archive_path)?;
53 let reader = BufReader::new(file);
54
55 if extension == "zip" {
56 println!("Extracting ZIP archive...");
57 let mut archive = zip::ZipArchive::new(reader)?;
58
59 for i in 0..archive.len() {
60 let mut file = archive.by_index(i)?;
61 let outpath = match file.enclosed_name() {
62 Some(path) => target_dir.join(path),
63 None => continue,
64 };
65
66 if file.name().ends_with('/') {
67 std::fs::create_dir_all(&outpath)?;
68 } else {
69 if let Some(p) = outpath.parent()
70 && !p.exists() {
71 std::fs::create_dir_all(p)?;
72 }
73 let mut outfile = File::create(&outpath)?;
74 copy(&mut file, &mut outfile)?;
75 }
76
77 #[cfg(unix)]
78 {
79 use std::os::unix::fs::PermissionsExt;
80 if let Some(mode) = file.unix_mode() {
81 std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?;
82 }
83 }
84 }
85 } else if extension == "tar.gz" {
86 println!("Extracting TAR.GZ archive...");
87 let tar_gz = flate2::read::GzDecoder::new(reader);
88 let mut archive = tar::Archive::new(tar_gz);
89 archive.unpack(&target_dir)?;
90 }
91
92 if let Err(e) = std::fs::remove_file(&archive_path) {
93 eprintln!("Warning: Failed to remove temporary archive file: {}", e);
94 }
95
96 println!("Extraction completed. Files stored in: {:?}", target_dir);
97
98 Ok(target_dir)
99}