use std::{ env, fs::File, io::{Error, ErrorKind, Read, Seek, Write}, path::PathBuf, str::FromStr, }; use anyhow::Result; use directories::BaseDirs; use url::Url; use zip::ZipArchive; use crate::{ github::{FetchResult, Fetcher, GitHub, Method}, types::{LuneReleaseData, LuneReleaseFetched}, }; fn parse_release_data(data: &LuneReleaseData) -> LuneReleaseFetched { const TARGET: &str = "download::parse_release_data"; tracing::info!(target: TARGET, "Parsing received LuneReleaseData"); let unknown = &String::from("UNKNOWN"); let mut version = unknown.to_owned(); let mut download = unknown.to_owned(); let mut artifact_name = unknown.to_owned(); tracing::info!( target: TARGET, "Defaulting to `unknown` values before parsing" ); for asset in &data.assets { let parts = asset .name .split("-") .map(|s| s.to_string()) .collect::>(); // [0] => always "lune" // [1] => version // [2] => platform // [3] => arch .. ".zip" if parts[2] == env::consts::OS && parts[3].trim_end_matches(".zip") == env::consts::ARCH { tracing::info!( target: TARGET, "Supported platform found, overwriting `unknown` values" ); version = (&parts[1]).to_owned(); download = (&asset.browser_download_url).to_owned(); artifact_name = (&asset.name).to_owned(); } else { tracing::warn!( target: TARGET, "Supported platform not found for asset {:?}", asset ); } } tracing::info!(target: TARGET, "Done."); LuneReleaseFetched { version, download, artifact_name, raw: data.clone(), } } pub fn download_release() -> Result<(PathBuf, LuneReleaseFetched)> { const TARGET: &str = "download::download_release"; tracing::info!(target: TARGET, "Initializing routine"); let parsed_release_data = parse_release_data(&GitHub::new(("filiptibell", "lune")).fetch_releases()?); tracing::info!(target: TARGET, "Received API resp and parsed release data"); tracing::info!(target: TARGET, "Got lune {}", parsed_release_data.version); let fetcher = Fetcher::new(); tracing::info!( target: TARGET, "Proceeding to download release from API resp (url = {})", &parsed_release_data.download ); let resp = fetcher.fetch::<_, ()>( Method::Get, Url::from_str(&parsed_release_data.download.as_str())?, false, )?; match resp { FetchResult::Response(res) => { tracing::info!( target: TARGET, "Received download resp, proceeding to write zip to disk at {}", &parsed_release_data.artifact_name ); let mut zip_archive = File::create(&parsed_release_data.artifact_name)?; let mut bytes = res .into_reader() .bytes() .map(|elem| elem.unwrap()) .collect::>(); zip_archive.write_all(&mut bytes)?; tracing::info!(target: TARGET, "Successfully downloaded release"); } FetchResult::Result(_) => unreachable!("Fetcher returned Result instead of Response"), }; Ok(( PathBuf::from(&parsed_release_data.artifact_name), parsed_release_data, )) } pub fn install_lune(lune_archive_reader: impl Read + Seek, meta: LuneReleaseFetched) -> Result<()> { const TARGET: &str = "download::install_lune"; tracing::info!(target: TARGET, "Initializing routine"); let dir_name = meta.artifact_name.trim_end_matches(".zip"); tracing::info!(target: TARGET, "Proceeding to extract release zip"); let mut lune_zip = ZipArchive::new(lune_archive_reader)?; lune_zip.extract(dir_name)?; tracing::info!(target: TARGET, "Successfully extracted release zip"); let bin_name = match env::consts::OS { "windows" => "lune.exe", "macos" | "linux" => "lune", _ => panic!("unsupported platform"), }; let bin_base_dir = BaseDirs::new() .ok_or(Error::new( ErrorKind::NotFound, "failed to create BaseDirs instance", ))? .home_dir() .join("bin"); if !bin_base_dir.exists() { tracing::warn!(target: TARGET, "bin_base_dir nonexistent, creating"); std::fs::create_dir(&bin_base_dir)?; } tracing::warn!( target: TARGET, "macOS and windows support for this action is incomplete" ); // TODO: macOS and windows support let lune_bin_path = bin_base_dir.join(bin_name); File::create(&lune_bin_path)?; std::fs::rename(PathBuf::from(dir_name).join(bin_name), &lune_bin_path)?; tracing::info!( target: TARGET, "Installed lune bin to {}", lune_bin_path.to_string_lossy() ); Ok(()) }