1use std::{
6 fs::{File, create_dir_all},
7 io::Read,
8 path::PathBuf,
9 process::{Command, Stdio},
10};
11
12use anyhow::{Context, Result};
13use flate2::read::GzDecoder;
14use log::{error, info};
15use rayon::prelude::*;
16
17use crate::{cmd::ensure_success, ui::get_progress_bar};
18
19const AUR_PKGBASE_URL: &str = "https://aur.archlinux.org/pkgbase.gz";
20const AUR_GIT_MIRROR_URL: &str = "https://github.com/archlinux/aur.git";
21
22#[derive(Clone, Debug)]
26pub struct AurDownloader {
27 pub dest: PathBuf,
29}
30
31impl AurDownloader {
32 pub fn download_packages(&self) -> Result<()> {
35 self.update_or_clone()?;
36 self.parallel_extract_files()?;
37 Ok(())
38 }
39
40 fn update_or_clone(&self) -> Result<()> {
42 if self.repo_dir().exists() {
43 info!("Updating aur.git mirror...");
44 let output = Command::new("git")
45 .arg("-C")
46 .arg(self.repo_dir())
47 .args(["fetch", "--depth=1", "--prune"])
48 .output()
49 .context("Failed to update aur.git.")?;
50 ensure_success(&output).context("Failed to update aur.git.")?;
51 } else {
52 create_dir_all(self.download_dir())?;
53 info!("Cloning aur.git mirror...");
54 let output = Command::new("git")
55 .args([
56 "clone",
59 "--mirror",
60 "--depth=1",
61 "--no-single-branch",
62 AUR_GIT_MIRROR_URL,
63 ])
64 .arg(self.repo_dir())
65 .output()
66 .context("Failed to clone aur.git.")?;
67 ensure_success(&output).context("Failed to clone aur.git.")?;
68 }
69 Ok(())
70 }
71
72 fn parallel_extract_files(&self) -> Result<()> {
74 let packages: Vec<String> = get_packages_list()?;
75
76 let progress_bar = get_progress_bar(packages.len() as u64);
77
78 create_dir_all(self.target_dir())?;
79
80 let results: Vec<Result<()>> = packages
81 .par_iter()
82 .map(|pkg| {
83 let pkg_dir = self.target_dir().join(pkg);
84 create_dir_all(&pkg_dir)
85 .context(format!("Failed to create package directory {pkg_dir:?}."))?;
86
87 for file_type in [".SRCINFO", "PKGBUILD"] {
88 let out_file = File::create(pkg_dir.join(file_type))?;
89 let output = Command::new("git")
90 .arg("show")
91 .arg(format!("{pkg}:{file_type}"))
92 .current_dir(self.repo_dir())
93 .stdout(Stdio::from(out_file))
94 .output()
95 .context(format!(
96 "Failed to extract {file_type:?} from AUR package {pkg:?}."
97 ))?;
98 ensure_success(&output).context(format!(
99 "Failed to extract {file_type:?} for package {pkg:?}."
100 ))?;
101 }
102 progress_bar.inc(1);
103 Ok(())
104 })
105 .collect();
106
107 progress_bar.finish_with_message("All files extracted.");
108
109 for error in results.into_iter().filter_map(Result::err) {
111 error!("{error:?}");
112 }
113
114 Ok(())
115 }
116
117 fn download_dir(&self) -> PathBuf {
118 self.dest.join("download")
119 }
120
121 fn repo_dir(&self) -> PathBuf {
122 self.download_dir().join("aur")
123 }
124
125 fn target_dir(&self) -> PathBuf {
126 self.dest.join("aur")
127 }
128}
129
130fn get_packages_list() -> Result<Vec<String>> {
132 let resp = reqwest::blocking::get(AUR_PKGBASE_URL)?.error_for_status()?;
133 let bytes = resp.bytes()?;
134 let mut decoder = GzDecoder::new(&bytes[..]);
135 let mut aur_packages_raw = String::new();
136 decoder.read_to_string(&mut aur_packages_raw)?;
137 Ok(aur_packages_raw.lines().map(|s| s.to_string()).collect())
138}