use std::fs; use std::path::Path; use std::process::Command; use std::io::{self, Write}; use std::cell::RefCell; use super::get_aeropkg_base; use crate::utils::parser::{self, env::get_install_env, repoinfo, pkginfo, env::get_custom_env}; use crate::utils::shell; use crate::commands::link::link; use crate::commands::run::config::config; #[derive(Default)] struct DownloadSummary { no_changes: Vec, updated: Vec<(String, u64)>, errors: Vec<(String, String)>, } thread_local! { static SUMMARY: RefCell = RefCell::new(DownloadSummary::default()); } pub fn print_download_summary() { print!("\r\x1B[K"); io::stdout().flush().unwrap(); SUMMARY.with(|s| { let summary = s.borrow(); if !summary.no_changes.is_empty() { println!("No changes: {}", summary.no_changes.join(", ")); } if !summary.updated.is_empty() { println!("Updated:"); for (pkg, count) in &summary.updated { println!(" {}: {} files", pkg, count); } } if !summary.errors.is_empty() { eprintln!("Errors:"); for (pkg, err) in &summary.errors { eprintln!(" {}: {}", pkg, err); } } drop(summary); s.replace(DownloadSummary::default()); }); } pub fn download_packages( repo: Option<&str>, packages: &[String], force: bool, recursive: bool, ) { for pkgname in packages { let effective_repo = match repo { Some(r) => &r.to_string(), None => &pkginfo::get_priority_repo(pkgname), }; download(effective_repo, pkgname, force, recursive); let pkg_md_path = &crate::commands::get_aeropkg_var().join(format!("{}/{}.md", effective_repo, pkgname)); let full_env = &get_custom_env(effective_repo, pkgname, pkg_md_path); shell::run_install_repo_hook_script(effective_repo, full_env); shell::run_install_hook_script(full_env); } } pub fn download(repo: &String, pkgname: &String, force: bool, recursive: bool) { let pkg_md_path = &crate::commands::get_aeropkg_var().join(format!("{}/{}.md", repo, pkgname)); check_build_dependency(repo, pkg_md_path, force, recursive); check_run_dependency(pkg_md_path, force, recursive); let source = repoinfo::get_download_source(repo); if source.is_empty() { let msg = format!("Download source not found for repo '{}'", repo); SUMMARY.with(|s| s.borrow_mut().errors.push((pkgname.clone(), msg))); return; } let remote_path = format!("{}/{}/", source.trim_end_matches('/'), pkgname); let local_path = format!("/pkg/{}/{}", repo, pkgname); if let Err(e) = std::fs::create_dir_all(&local_path) { let msg = format!("Failed to create directory {}: {}", local_path, e); SUMMARY.with(|s| s.borrow_mut().errors.push((pkgname.clone(), msg))); return; } let local_path_sync = format!("{}/", local_path); print!("\r\x1B[KDownloading {}", pkgname); io::stdout().flush().unwrap(); let mut cmd = Command::new("rsync"); cmd.arg("-azHX").arg("--stats"); if force { cmd.arg("--delete"); } cmd.arg(&remote_path); cmd.arg(&local_path_sync); let result = cmd.output(); match result { Ok(output) => { let stdout = String::from_utf8_lossy(&output.stdout); let transferred = stdout .lines() .find(|line| line.contains("Number of regular files transferred:")) .and_then(|line| line.split(':').nth(1)) .map(|s| s.trim().parse::().unwrap_or(0)) .unwrap_or(0); if transferred == 0 && output.status.success() { SUMMARY.with(|s| s.borrow_mut().no_changes.push(pkgname.clone())); } else if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let err_msg = stderr.lines().rev().find(|l| !l.trim().is_empty()).unwrap_or("unknown error").to_string(); SUMMARY.with(|s| s.borrow_mut().errors.push((pkgname.clone(), err_msg))); } else { SUMMARY.with(|s| s.borrow_mut().updated.push((pkgname.clone(), transferred))); } } Err(e) => { SUMMARY.with(|s| s.borrow_mut().errors.push((pkgname.clone(), e.to_string()))); } } config(repo, pkgname); let full_env = get_install_env(repo, pkgname, pkg_md_path, "build"); if full_env.get("disable").is_some() { let _ = fs::File::create(crate::commands::get_aeropkg_base().join(repo).join(pkgname).join("disabled")); } else { link(repo, pkgname) } } fn check_build_dependency(repo: &String, pkg_md_path: &Path, force: bool, recursive: bool) { let deps = match parser::get_build_deps(pkg_md_path) { Ok(deps) => deps, Err(_) => return, }; for dep in deps.split_whitespace() { let dep = dep.trim(); if dep.is_empty() { continue } if !get_aeropkg_base().join(repo).join(dep).exists() || recursive { download(repo, &dep.to_string(), force, recursive); } } } fn check_run_dependency(pkg_md_path: &Path, force: bool, recursive: bool) { let deps = match parser::get_run_deps(pkg_md_path) { Ok(deps) => deps, Err(_) => return, }; let repo_list = parser::repoinfo::get_repo_list(); for dep in deps.split_whitespace() { let dep = dep.trim(); if dep.is_empty() { continue } let found_repo = repo_list.iter().find(|repo_name| { get_aeropkg_base().join(repo_name).join(dep).exists() }); match found_repo { Some(found) if recursive => { download(found, &dep.to_string(), force, recursive); } None => { download(&pkginfo::get_priority_repo(&dep.to_string()), &dep.to_string(), force, recursive); } _ => {} } } }