use std::{ cell::RefCell, io::{PipeWriter, Write}, path::{Path, PathBuf}, rc::Rc, time::Duration, }; use crate::cook::fs; use pkg::{ PackageName, RemotePackage, RepoManager, Repository, callback::{Callback, PlainCallback, SilentCallback}, net_backend::{CurlBackend, DownloadBackend}, }; // TODO: This is a workaround, but as long as whole // fetch operation is in single thread, this is ok thread_local! { static BINARY_REPO: RefCell> = RefCell::new(None); } fn load_cached_repo(path: &Path) -> Option { let metadata = std::fs::metadata(path).ok()?; if !crate::config::get_config().cook.offline { let stale_time = std::time::SystemTime::now().checked_sub(Duration::from_secs(8 * 3600))?; if metadata.modified().ok()? < stale_time { // stale cache let _ = std::fs::remove_file(path); return None; } } let toml_str = std::fs::read_to_string(path).ok()?; Repository::from_toml(&toml_str).ok() } fn init_binary_repo() -> (RepoManager, Repository) { let callback = Rc::new(RefCell::new(SilentCallback::new())); let download_backend = CurlBackend::new().expect("Curl not found"); let mut repo = RepoManager::new(callback, Box::new(download_backend)); let target = redoxer::target(); repo.add_remote(crate::REMOTE_PKG_SOURCE, target) .expect("Unable to add remote"); let repo_path = PathBuf::from("build/remotes"); repo.set_download_path(repo_path.clone()); repo.sync_keys().expect("Unable to sync keys"); let repo_toml = load_cached_repo(&repo_path.join(format!("{target}_repo.toml"))) .unwrap_or_else(|| { let repo = download_repo(&repo, repo_path) .map_err(|e| { eprintln!( "Unable to load server repo.toml, all recipes will build from source: {e}" ); e }) .unwrap_or_default(); repo }); // reset here to not clobber pty repo.callback = Rc::new(RefCell::new(PlainCallback::new())); (repo, repo_toml) } fn download_repo(repo: &RepoManager, repo_path: PathBuf) -> crate::Result { let (toml_str, _) = repo.get_package_toml(&PackageName::new("repo").unwrap())?; let repo = Repository::from_toml(&toml_str)?; let target = redoxer::target(); fs::serialize_and_write(&repo_path.join(format!("{target}_repo.toml")), &repo)?; Ok(repo) } pub fn get_binary_repo() -> (RepoManager, Repository) { BINARY_REPO.with(|cell| { let mut opt = cell.borrow_mut(); if opt.is_none() { *opt = Some(init_binary_repo()); } let (repo, repo_toml) = opt.as_ref().unwrap(); ((*repo).clone(), repo_toml.clone()) }) } pub struct PlainPtyCallback { size: u64, unknown_size: bool, pos: u64, fetch_processed: usize, fetch_total: usize, interactive: bool, download_file: Option, pty: PipeWriter, } impl PlainPtyCallback { pub fn new(pty: PipeWriter) -> Self { Self { size: 0, unknown_size: false, pos: 0, fetch_processed: 0, fetch_total: 0, interactive: false, download_file: None, pty, } } /// Set if user require to agree on terminal pub fn set_interactive(&mut self, enabled: bool) { self.interactive = enabled; } fn flush(&self) { let _ = std::io::stderr().flush(); } pub fn format_size(bytes: u64) -> String { if bytes == 0 { return "0 B".to_string(); } const UNITS: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"]; let i = (bytes as f64).log(1024.0).floor() as usize; let size = bytes as f64 / 1024.0_f64.powi(i as i32); format!("{:.2} {}", size, UNITS[i]) } fn downloading_str(&self) -> &'static str { "Downloading" } } const RESET_LINE: &str = "\r\x1b[2K"; impl Callback for PlainPtyCallback { fn fetch_start(&mut self, initial_count: usize) { self.fetch_total = 0; self.fetch_processed = 0; self.fetch_package_increment(0, initial_count); } fn fetch_package_name(&mut self, pkg_name: &PackageName) { // resuming after fetch_package_increment let _ = write!(&self.pty, " {}", pkg_name.as_str()); self.flush(); } fn fetch_package_increment(&mut self, added_processed: usize, added_count: usize) { self.fetch_processed += added_processed; self.fetch_total += added_count; let _ = write!( &self.pty, "{RESET_LINE}Fetching: [{}/{}]", self.fetch_processed, self.fetch_total ); self.flush(); } fn fetch_end(&mut self) { if self.fetch_processed == self.fetch_total { let _ = writeln!(&self.pty, "{RESET_LINE}Fetch complete."); } else { let _ = writeln!(&self.pty, "{RESET_LINE}Fetch incomplete."); } } fn download_start(&mut self, length: u64, file: &str) { self.size = length; self.unknown_size = length == 0; self.pos = 0; if !self.unknown_size { let _ = write!(&self.pty, "{RESET_LINE}{} {file}", self.downloading_str()); self.download_file = Some(file.to_string()); self.flush(); } } fn download_increment(&mut self, downloaded: u64) { self.pos += downloaded; if self.unknown_size { self.size += downloaded; } if self.unknown_size { return; } // keep using MB for consistency let pos_mb = self.pos as f64 / 1_048_576.0; let size_mb = self.size as f64 / 1_048_576.0; let file_name = self .download_file .as_ref() .map(|s| s.as_str()) .unwrap_or(""); let _ = write!( &self.pty, "{RESET_LINE}{} {} [{:.2} MB / {:.2} MB]", self.downloading_str(), file_name, pos_mb, size_mb ); self.flush(); } fn download_end(&mut self) { if !self.unknown_size { let _ = writeln!(&self.pty, ""); self.download_file = None; } } fn install_extract(&mut self, remote_pkg: &RemotePackage) { let _ = writeln!(&self.pty, "Extracting {}...", remote_pkg.package.name); self.flush(); } }