From b20307a8205429831113981bd0272767956e28d5 Mon Sep 17 00:00:00 2001 From: Rabindra Dhakal Date: Tue, 5 Nov 2024 18:08:02 +0545 Subject: [PATCH 1/2] feat(install): implement local package install --- src/core/file.rs | 11 +- src/core/util.rs | 36 ++++- src/package/appimage.rs | 14 +- src/package/install.rs | 279 +++++++++++++++++++++----------------- src/package/mod.rs | 51 +++++-- src/package/update.rs | 1 - src/registry/fetcher.rs | 2 +- src/registry/installed.rs | 4 +- src/registry/mod.rs | 11 +- src/registry/storage.rs | 56 +++++--- 10 files changed, 288 insertions(+), 177 deletions(-) diff --git a/src/core/file.rs b/src/core/file.rs index 1ccdcd6..7ef918c 100644 --- a/src/core/file.rs +++ b/src/core/file.rs @@ -1,10 +1,8 @@ -use std::{ - fs::File, - io::{BufReader, Read}, -}; +use std::io::{BufReader, Read}; use super::constant::{APPIMAGE_MAGIC_BYTES, ELF_MAGIC_BYTES, FLATIMAGE_MAGIC_BYTES}; +#[derive(PartialEq, Eq)] pub enum FileType { AppImage, FlatImage, @@ -12,7 +10,10 @@ pub enum FileType { Unknown, } -pub fn get_file_type(file: &mut BufReader) -> FileType { +pub fn get_file_type(file: &mut BufReader) -> FileType +where + T: std::io::Read, +{ let mut magic_bytes = [0u8; 12]; if file.read_exact(&mut magic_bytes).is_ok() { if magic_bytes[8..] == APPIMAGE_MAGIC_BYTES { diff --git a/src/core/util.rs b/src/core/util.rs index d6fe94a..5ead3b5 100644 --- a/src/core/util.rs +++ b/src/core/util.rs @@ -1,5 +1,7 @@ use std::{ - env, mem, + env, + io::Write, + mem, path::{Path, PathBuf}, }; @@ -13,6 +15,8 @@ use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, }; +use crate::warn; + use super::{ color::{Color, ColorExt}, constant::{BIN_PATH, CACHE_PATH, INSTALL_TRACK_PATH, PACKAGES_PATH, REGISTRY_PATH}, @@ -88,9 +92,12 @@ pub fn parse_size(size_str: &str) -> Option { let size_str = size_str.trim(); let units = [ ("B", 1u64), - ("KB", 1024u64), - ("MB", 1024u64 * 1024), - ("GB", 1024u64 * 1024 * 1024), + ("KB", 1000u64), + ("MB", 1000u64 * 1000), + ("GB", 1000u64 * 1000 * 1000), + ("KiB", 1024u64), + ("MiB", 1024u64 * 1024), + ("GiB", 1024u64 * 1024 * 1024), ]; for (unit, multiplier) in &units { @@ -348,3 +355,24 @@ pub fn download_progress_style(with_msg: bool) -> ProgressStyle { ) .progress_chars("━━") } + +#[derive(PartialEq, Eq)] +pub enum AskType { + Warn, + Normal, +} + +pub fn interactive_ask(ques: &str, ask_type: AskType) -> Result { + if ask_type == AskType::Warn { + warn!("{ques}"); + } else { + println!("{ques}"); + } + + std::io::stdout().flush()?; + + let mut response = String::new(); + std::io::stdin().read_line(&mut response)?; + + Ok(response.trim().to_owned()) +} diff --git a/src/package/appimage.rs b/src/package/appimage.rs index 3dcdc8a..1a5bc37 100644 --- a/src/package/appimage.rs +++ b/src/package/appimage.rs @@ -161,9 +161,9 @@ pub async fn integrate_appimage( match resolve_and_extract(&squashfs, node, &output_path, &mut HashSet::new()) { Ok(()) => { if extension == "png" { - process_icon(&output_path, &package.bin_name, data_path).await?; + process_icon(&output_path, &package.pkg_name, data_path).await?; } else { - process_desktop(&output_path, &package.bin_name, &package.name, data_path) + process_desktop(&output_path, &package.pkg_name, &package.pkg, data_path) .await?; } } @@ -315,8 +315,8 @@ pub async fn integrate_using_remote_files(package: &Package, file_path: &Path) - let desktop_content = match desktop_content { Some(content) => content, None => create_default_desktop_entry( - &package.bin_name, - &package.name, + &package.pkg_name, + &package.pkg, &package.category.replace(',', ";"), ), }; @@ -324,11 +324,11 @@ pub async fn integrate_using_remote_files(package: &Package, file_path: &Path) - fs::write(&desktop_output_path, &desktop_content).await?; try_join!( - process_icon(&icon_output_path, &package.bin_name, data_path), + process_icon(&icon_output_path, &package.pkg_name, data_path), process_desktop( &desktop_output_path, - &package.bin_name, - &package.name, + &package.pkg_name, + &package.pkg, data_path ) )?; diff --git a/src/package/install.rs b/src/package/install.rs index b54ab79..56639a4 100644 --- a/src/package/install.rs +++ b/src/package/install.rs @@ -9,7 +9,12 @@ use std::{ use anyhow::{Context, Result}; use futures::StreamExt; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; -use tokio::{fs, io::AsyncWriteExt, sync::Mutex}; +use reqwest::Url; +use tokio::{ + fs, + io::{AsyncReadExt, AsyncWriteExt}, + sync::Mutex, +}; use crate::{ core::{ @@ -19,7 +24,6 @@ use crate::{ util::{calculate_checksum, download_progress_style, validate_checksum}, }, registry::installed::InstalledPackages, - warn, }; use super::{ @@ -34,14 +38,14 @@ pub struct Installer { } impl Installer { - pub fn new(package: &ResolvedPackage, install_path: PathBuf) -> Self { + pub fn new(package: &ResolvedPackage) -> Self { let temp_path = PACKAGES_PATH .join("tmp") .join(package.package.full_name('-')) .with_extension("part"); Self { resolved_package: package.to_owned(), - install_path, + install_path: Path::new("").to_path_buf(), temp_path, } } @@ -55,7 +59,6 @@ impl Installer { portable_home: Option, portable_config: Option, multi_progress: Arc, - yes: bool, ) -> Result<()> { let package = &self.resolved_package.package; @@ -74,87 +77,15 @@ impl Installer { ))?; } - let temp_path = &self.temp_path; - let client = reqwest::Client::new(); - let downloaded_bytes = if temp_path.exists() { - let meta = fs::metadata(&temp_path).await?; - meta.len() + if Url::parse(&package.download_url).is_ok() { + self.download_remote_package(multi_progress.clone(), &prefix) + .await?; } else { - 0 - }; - - let response = client - .get(&package.download_url) - .header("Range", format!("bytes={}-", downloaded_bytes)) - .send() - .await - .context(format!("{}: Failed to download package", prefix))?; - let total_size = response - .content_length() - .map(|cl| cl + downloaded_bytes) - .unwrap_or(0); - - let download_progress = multi_progress.insert_from_back(1, ProgressBar::new(0)); - download_progress.set_style(download_progress_style(true)); - - download_progress.set_length(total_size); - download_progress.set_message(prefix.clone()); - - if !response.status().is_success() { - return Err(anyhow::anyhow!( - "{} Download failed {:?}", - prefix, - response.status().color(Color::Red), - )); - } - - { - let mut file = fs::OpenOptions::new() - .write(true) - .create(true) - .append(true) - .open(&temp_path) - .await - .context(format!("{}: Failed to open temp file for writing", prefix))?; - let mut stream = response.bytes_stream(); - - while let Some(chunk) = stream.next().await { - let chunk = chunk.context(format!("{}: Failed to read chunk", prefix))?; - file.write_all(&chunk).await?; - download_progress.inc(chunk.len() as u64); - } - download_progress.finish(); - file.flush().await?; + self.copy_local_package(multi_progress.clone(), &prefix) + .await?; } - if package.bsum == "null" { - warn!( - "Missing checksum for {}. Installing anyway.", - package.full_name('/').color(Color::BrightBlue) - ); - } else { - let result = validate_checksum(&package.bsum, &self.temp_path).await; - if result.is_err() { - if yes { - warn!("Checksum verification failed. Installing anyway."); - } else { - eprint!( - "\n{}: Checksum verification failed. Do you want to remove the package? (y/n): ", - prefix - ); - std::io::stdout().flush()?; - - let mut response = String::new(); - std::io::stdin().read_line(&mut response)?; - - if response.trim().eq_ignore_ascii_case("y") { - tokio::fs::remove_file(&temp_path).await?; - return Err(anyhow::anyhow!("Checksum verification failed.")); - } - } - } - } - let checksum = calculate_checksum(temp_path).await?; + let checksum = calculate_checksum(&self.temp_path).await?; self.install_path = package.get_install_path(&checksum); if let Some(parent) = self.install_path.parent() { @@ -171,6 +102,8 @@ impl Installer { let mut file = BufReader::new(File::open(&self.install_path)?); let file_type = get_file_type(&mut file); + let warn = multi_progress.insert(1, ProgressBar::new(0)); + warn.set_style(ProgressStyle::default_bar().template("{msg}").unwrap()); match file_type { FileType::AppImage => { if integrate_appimage(&mut file, package, &self.install_path) @@ -178,7 +111,7 @@ impl Installer { .is_ok() { setup_portable_dir( - &package.bin_name, + &package.pkg_name, &self.install_path, portable, portable_home, @@ -186,7 +119,11 @@ impl Installer { ) .await?; } else { - warn!("{}: Failed to integrate AppImage", prefix); + warn.finish_with_message(format!( + "{}: {}", + prefix, + "Failed to integrate AppImage".color(Color::BrightYellow) + )); }; } FileType::FlatImage => { @@ -195,7 +132,7 @@ impl Installer { .is_ok() { setup_portable_dir( - &package.bin_name, + &package.pkg_name, Path::new(&format!(".{}", self.install_path.display())), None, None, @@ -203,7 +140,11 @@ impl Installer { ) .await?; } else { - warn!("{}: Failed to integrate FlatImage", prefix); + warn.finish_with_message(format!( + "{}: {}", + prefix, + "Failed to integrate FlatImage".color(Color::BrightYellow) + )); }; } _ => {} @@ -217,12 +158,7 @@ impl Installer { } let installed_progress = multi_progress.insert_from_back(1, ProgressBar::new(0)); - installed_progress.set_style( - ProgressStyle::default_bar() - .template("{msg}") - .unwrap() - .progress_chars("##-"), - ); + installed_progress.set_style(ProgressStyle::default_bar().template("{msg}").unwrap()); installed_progress.finish_with_message(format!( "[{}/{}] Installed {}", (idx + 1).color(Color::Green), @@ -232,9 +168,8 @@ impl Installer { if !package.note.is_empty() { println!( - "{}: [{}] {}", + "{}: {}", prefix, - "Note".color(Color::Magenta), package .note .replace("
", "\n ") @@ -245,6 +180,124 @@ impl Installer { Ok(()) } + async fn download_remote_package( + &self, + multi_progress: Arc, + prefix: &str, + ) -> Result<()> { + let prefix = prefix.to_owned(); + let package = &self.resolved_package.package; + let temp_path = &self.temp_path; + let client = reqwest::Client::new(); + let downloaded_bytes = if temp_path.exists() { + let meta = fs::metadata(&temp_path).await?; + meta.len() + } else { + 0 + }; + + let response = client + .get(&package.download_url) + .header("Range", format!("bytes={}-", downloaded_bytes)) + .send() + .await + .context(format!("{}: Failed to download package", prefix))?; + let total_size = response + .content_length() + .map(|cl| cl + downloaded_bytes) + .unwrap_or(0); + + let download_progress = multi_progress.insert_from_back(1, ProgressBar::new(0)); + download_progress.set_style(download_progress_style(true)); + + download_progress.set_length(total_size); + download_progress.set_message(prefix.clone()); + + if !response.status().is_success() { + return Err(anyhow::anyhow!( + "{} Download failed {:?}", + prefix, + response.status().color(Color::Red), + )); + } + + let mut file = fs::OpenOptions::new() + .create(true) + .append(true) + .open(&temp_path) + .await + .context(format!("{}: Failed to open temp file for writing", prefix))?; + let mut stream = response.bytes_stream(); + + while let Some(chunk) = stream.next().await { + let chunk = chunk.context(format!("{}: Failed to read chunk", prefix))?; + file.write_all(&chunk).await?; + download_progress.inc(chunk.len() as u64); + } + download_progress.finish(); + file.flush().await?; + + let warn_bar = multi_progress.insert_from_back(1, ProgressBar::new(0)); + warn_bar.set_style(ProgressStyle::default_bar().template("{msg}").unwrap()); + if package.bsum == "null" { + warn_bar.finish_with_message(format!( + "{}: {}", + prefix, + "Missing checksum. Installing anyway.".color(Color::BrightYellow) + )); + } else { + let result = validate_checksum(&package.bsum, &self.temp_path).await; + if result.is_err() { + warn_bar.finish_with_message(format!( + "{}: {}", + prefix, + "Checksum verification failed. Installing anyway.".color(Color::BrightYellow) + )); + } + } + + Ok(()) + } + + async fn copy_local_package( + &self, + multi_progress: Arc, + prefix: &str, + ) -> Result<()> { + let temp_path = &self.temp_path; + let prefix = prefix.to_owned(); + let package = &self.resolved_package.package; + + let download_progress = multi_progress.insert_from_back(1, ProgressBar::new(0)); + download_progress.set_style(download_progress_style(true)); + + let total_size = package.size.parse::().unwrap_or_default(); + download_progress.set_length(total_size); + download_progress.set_message(prefix.clone()); + + let mut file = fs::OpenOptions::new() + .create(true) + .append(true) + .open(&temp_path) + .await + .context(format!("{}: Failed to open temp file for writing", prefix))?; + let mut source = fs::File::open(&package.download_url).await?; + let mut buffer = vec![0u8; 8096]; + + while let Ok(n) = source.read(&mut buffer).await { + if n == 0 { + break; + } + + file.write_all(&buffer[..n]).await?; + download_progress.inc(n as u64); + } + download_progress.finish(); + file.flush().await?; + + Ok(()) + } + async fn save_file(&self) -> Result<()> { let install_path = &self.install_path; let temp_path = &self.temp_path; @@ -261,36 +314,16 @@ impl Installer { async fn symlink_bin(&self) -> Result<()> { let package = &self.resolved_package.package; let install_path = &self.install_path; - let symlink_path = &BIN_PATH.join(&package.bin_name); + let symlink_path = &BIN_PATH.join(&package.pkg_name); if symlink_path.exists() { if let Ok(link) = symlink_path.read_link() { - if &link != install_path { - if let Ok(parent) = link.strip_prefix(&*PACKAGES_PATH) { - let package_name = - parent.parent().unwrap().to_string_lossy()[9..].replacen("-", "/", 1); - - if package_name == package.full_name('-') { - fs::remove_dir_all(link.parent().unwrap()).await?; - } else { - warn!( - "The package {} owns the binary {}", - package_name, &package.bin_name - ); - print!( - "Do you want to switch to {} (y/N)? ", - package.full_name('/').color(Color::Blue) - ); - std::io::stdout().flush()?; - - let mut response = String::new(); - std::io::stdin().read_line(&mut response)?; - - if !response.trim().eq_ignore_ascii_case("y") { - return Ok(()); - } - } - }; - } + if let Ok(parent) = link.strip_prefix(&*PACKAGES_PATH) { + let package_name = &parent.parent().unwrap().to_string_lossy()[9..]; + + if package_name == package.full_name('-') { + fs::remove_dir_all(link.parent().unwrap()).await?; + } + }; } fs::remove_file(symlink_path).await?; } diff --git a/src/package/mod.rs b/src/package/mod.rs index 51a0271..916c826 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -5,7 +5,10 @@ pub mod remove; pub mod run; pub mod update; -use std::{path::PathBuf, sync::Arc}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; use anyhow::Result; use indicatif::MultiProgress; @@ -13,12 +16,18 @@ use install::Installer; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; -use crate::{core::constant::PACKAGES_PATH, registry::installed::InstalledPackages}; +use crate::{ + core::{ + constant::PACKAGES_PATH, + util::{interactive_ask, AskType}, + }, + registry::installed::InstalledPackages, +}; #[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct Package { - pub name: String, - pub bin_name: String, + pub pkg: String, + pub pkg_name: String, pub description: String, pub note: String, pub version: String, @@ -27,14 +36,14 @@ pub struct Package { pub bsum: String, pub build_date: String, pub src_url: String, - pub web_url: String, + pub homepage: String, pub build_script: String, pub build_log: String, pub category: String, pub extra_bins: String, pub icon: String, pub desktop: Option, - pub bin_id: Option, + pub pkg_id: Option, pub family: Option, } @@ -55,10 +64,8 @@ impl ResolvedPackage { portable_home: Option, portable_config: Option, multi_progress: Arc, - yes: bool, ) -> Result<()> { - let install_path = self.package.get_install_path(&self.package.bsum[..8]); - let mut installer = Installer::new(self, install_path); + let mut installer = Installer::new(self); installer .execute( idx, @@ -68,7 +75,6 @@ impl ResolvedPackage { portable_home, portable_config, multi_progress, - yes, ) .await?; Ok(()) @@ -81,7 +87,7 @@ impl Package { } pub fn get_install_path(&self, checksum: &str) -> PathBuf { - self.get_install_dir(checksum).join(&self.bin_name) + self.get_install_dir(checksum).join(&self.pkg_name) } pub fn full_name(&self, join_char: char) -> String { @@ -90,7 +96,7 @@ impl Package { .to_owned() .map(|family| format!("{}{}", family, join_char)) .unwrap_or_default(); - format!("{}{}", family_prefix, self.name) + format!("{}{}", family_prefix, self.pkg) } } @@ -118,3 +124,24 @@ pub fn parse_package_query(query: &str) -> PackageQuery { collection, } } + +#[inline] +pub fn ask_package_info(name: &str, path: &Path, size: u64) -> Result { + let bin_name = interactive_ask("Binary Name:", AskType::Normal)?; + + let package = Package { + pkg: name.to_owned(), + pkg_name: bin_name, + size: size.to_string(), + download_url: path.to_string_lossy().to_string(), + ..Default::default() + }; + + let resolved_package = ResolvedPackage { + repo_name: "local".to_owned(), + collection: "local".to_string(), + package, + }; + + Ok(resolved_package) +} diff --git a/src/package/update.rs b/src/package/update.rs index 20056df..d0cba21 100644 --- a/src/package/update.rs +++ b/src/package/update.rs @@ -91,7 +91,6 @@ impl Updater { None, None, multi_progress.clone(), - true, ) .await?; update_count += 1; diff --git a/src/registry/fetcher.rs b/src/registry/fetcher.rs index d65df2d..80cfe16 100644 --- a/src/registry/fetcher.rs +++ b/src/registry/fetcher.rs @@ -49,7 +49,7 @@ impl MetadataFetcher { .map(|(key, packages)| { let package_map: HashMap> = packages.iter().fold(HashMap::new(), |mut acc, package| { - acc.entry(package.name.clone()).or_default().push(Package { + acc.entry(package.pkg.clone()).or_default().push(Package { family: package .download_url .split('/') diff --git a/src/registry/installed.rs b/src/registry/installed.rs index 50e0daa..9cdf7d3 100644 --- a/src/registry/installed.rs +++ b/src/registry/installed.rs @@ -91,9 +91,9 @@ impl InstalledPackages { let new_installed = InstalledPackage { repo_name: resolved_package.repo_name.to_owned(), collection: resolved_package.collection.to_string().to_owned(), - name: package.name, + name: package.pkg, family: package.family, - bin_name: package.bin_name, + bin_name: package.pkg_name, version: package.version, checksum: checksum.to_owned(), size: parse_size(&package.size).unwrap_or_default(), diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 5f98b0f..08a88ad 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -151,7 +151,7 @@ impl PackageRegistry { let formatted_name = format!( "{} ({}#{})", - package.bin_name.clone().color(Color::BrightGreen), + package.pkg_name.clone().color(Color::BrightGreen), package.clone().full_name('/').color(Color::BrightCyan), pkg.collection.clone().color(Color::BrightRed) ); @@ -161,7 +161,10 @@ impl PackageRegistry { "Description", package.description.clone().color(Color::BrightYellow), ), - ("Homepage", package.web_url.clone().color(Color::BrightBlue)), + ( + "Homepage", + package.homepage.clone().color(Color::BrightBlue), + ), ("Source", package.src_url.clone().color(Color::BrightBlue)), ( "Version", @@ -278,7 +281,7 @@ impl PackageRegistry { install_prefix.color(Color::Red), resolved_package.collection.color(Color::BrightGreen), package.full_name('/').color(Color::Blue), - package.name.color(Color::Blue), + package.pkg.color(Color::Blue), package.version.color(Color::Green), package.size.color(Color::Magenta) ); @@ -335,7 +338,7 @@ impl PackageRegistry { pub fn select_single_package(packages: &[ResolvedPackage]) -> Result<&ResolvedPackage> { info!( "Multiple packages available for {}", - packages[0].package.name.clone().color(Color::Blue) + packages[0].package.pkg.clone().color(Color::Blue) ); for (i, package) in packages.iter().enumerate() { println!( diff --git a/src/registry/storage.rs b/src/registry/storage.rs index a7d6e55..ba1b965 100644 --- a/src/registry/storage.rs +++ b/src/registry/storage.rs @@ -1,6 +1,7 @@ use std::{ collections::HashMap, - io::Write, + fs::File, + io::BufReader, sync::{ atomic::{AtomicU64, Ordering}, Arc, @@ -21,10 +22,13 @@ use crate::{ color::{Color, ColorExt}, config::CONFIG, constant::CACHE_PATH, - util::format_bytes, + file::{get_file_type, FileType}, + util::{build_path, format_bytes, interactive_ask, AskType}, }, error, - package::{parse_package_query, run::Runner, Package, PackageQuery, ResolvedPackage}, + package::{ + ask_package_info, parse_package_query, run::Runner, Package, PackageQuery, ResolvedPackage, + }, registry::installed::InstalledPackages, warn, }; @@ -80,7 +84,30 @@ impl PackageStorage { ) -> Result<()> { let resolved_packages: Result> = package_names .iter() - .map(|package_name| self.resolve_package(package_name, yes)) + .map(|package_name| { + if let Ok(package) = self.resolve_package(package_name, yes) { + Ok(package) + } else { + // check if a local package is provided instead + let package_path = build_path(package_name)?; + if package_path.is_file() { + let realpath = if package_path.is_symlink() { + package_path.read_link()? + } else { + package_path + }; + let file = File::open(&realpath)?; + let mut buf_reader = BufReader::new(&file); + if get_file_type(&mut buf_reader) != FileType::Unknown { + let package_name = realpath.file_name().unwrap().to_string_lossy(); + let size = file.metadata()?.len(); + let package = ask_package_info(&package_name, &realpath, size)?; + return Ok(package); + }; + }; + Err(anyhow::anyhow!("Package {} not found.", package_name)) + } + }) .collect(); let resolved_packages = resolved_packages?; @@ -150,7 +177,6 @@ impl PackageStorage { portable_home, portable_config, multi_progress, - yes, ) .await { @@ -179,7 +205,6 @@ impl PackageStorage { portable_home.clone(), portable_config.clone(), multi_progress.clone(), - yes, ) .await { @@ -300,7 +325,7 @@ impl PackageStorage { .flat_map(|(collection_key, map)| { map.get(pkg_name).into_iter().flat_map(|pkgs| { pkgs.iter().filter_map(|pkg| { - if pkg.name == pkg_name + if pkg.pkg == pkg_name && (query.family.is_none() || pkg.family.as_ref() == query.family.as_ref()) { @@ -342,9 +367,9 @@ impl PackageStorage { packages.iter().filter_map(|pkg| { let mut score = 0; let found_pkg_name = if case_sensitive { - pkg.name.clone() + pkg.pkg.clone() } else { - pkg.name.to_lowercase() + pkg.pkg.to_lowercase() }; if found_pkg_name == pkg_name { @@ -418,16 +443,11 @@ impl PackageStorage { let content_length = response.content_length().unwrap_or_default(); if content_length > 1_048_576 { - warn!( + let response = interactive_ask(&format!( "The build {} file is too large ({}). Do you really want to download and view it (y/N)? ", inspect_type, format_bytes(content_length).color(Color::Magenta) - ); - - std::io::stdout().flush()?; - let mut response = String::new(); - - std::io::stdin().read_line(&mut response)?; + ), AskType::Warn)?; if !response.trim().eq_ignore_ascii_case("y") { return Err(anyhow::anyhow!("")); @@ -465,13 +485,13 @@ impl PackageStorage { &[] }; let runner = if let Ok(resolved_pkg) = self.resolve_package(package_name, yes) { - let package_path = CACHE_PATH.join(&resolved_pkg.package.bin_name); + let package_path = CACHE_PATH.join(&resolved_pkg.package.pkg_name); Runner::new(&resolved_pkg, package_path, args) } else { let query = parse_package_query(package_name); let package_path = CACHE_PATH.join(&query.name); let mut resolved_pkg = ResolvedPackage::default(); - resolved_pkg.package.name = query.name; + resolved_pkg.package.pkg = query.name; resolved_pkg.package.family = query.family; // TODO: check all the repo for package instead of choosing the first From 131e07a3c7950c3bce52355a9765b6c34a2161a4 Mon Sep 17 00:00:00 2001 From: Rabindra Dhakal Date: Tue, 5 Nov 2024 20:45:21 +0545 Subject: [PATCH 2/2] refactor(config): move default soar dir, use without config file --- src/cli.rs | 4 +++ src/core/config.rs | 70 ++++++++++++++++++++++++------------------ src/core/file.rs | 2 +- src/core/util.rs | 23 ++++++++++++-- src/lib.rs | 8 +++-- src/package/install.rs | 2 +- src/package/mod.rs | 2 +- src/registry/mod.rs | 2 +- 8 files changed, 75 insertions(+), 38 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index ac1b70b..4223c15 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -175,4 +175,8 @@ pub enum Commands { /// Health check #[clap(name = "health")] Health, + + /// Generate config + #[clap(name = "defconfig")] + DefConfig, } diff --git a/src/core/config.rs b/src/core/config.rs index a9dcd9a..cf758ab 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -1,10 +1,12 @@ use std::{collections::HashMap, env::consts::ARCH, fs, path::PathBuf, sync::LazyLock}; +use anyhow::Result; use serde::{Deserialize, Serialize}; -use crate::core::color::{Color, ColorExt}; - -use super::{constant::REGISTRY_PATH, util::home_config_path}; +use super::{ + constant::REGISTRY_PATH, + util::{home_config_path, home_data_path}, +}; /// Application's configuration #[derive(Deserialize, Serialize)] @@ -50,18 +52,13 @@ impl Config { /// If the configuration file is not found, it generates a new default configuration. pub fn new() -> Self { let home_config = home_config_path(); - let pkg_config = PathBuf::from(home_config).join(env!("CARGO_PKG_NAME")); + let pkg_config = PathBuf::from(home_config).join("soar"); let config_path = pkg_config.join("config.json"); let content = match fs::read(&config_path) { Ok(content) => content, Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - fs::create_dir_all(&pkg_config).unwrap(); - eprintln!( - "{}\nGenerating default config at {}", - "Config not found".color(Color::BrightRed), - config_path.to_string_lossy().color(Color::Green) - ); - Config::generate(config_path) + let def_config = Self::default(); + serde_json::to_vec(&def_config).unwrap() } Err(e) => { panic!("Error reading config file: {:?}", e); @@ -70,37 +67,30 @@ impl Config { serde_json::from_slice(&content) .unwrap_or_else(|e| panic!("Failed to parse config file: {:?}", e)) } +} - fn generate(config_path: PathBuf) -> Vec { +impl Default for Config { + fn default() -> Self { let sources = HashMap::from([ - ("bin".to_owned(), format!("https://bin.ajam.dev/{ARCH}")), + ("bin".to_owned(), format!("https://bin.pkgforge.dev/{ARCH}")), ( "base".to_owned(), - format!("https://bin.ajam.dev/{ARCH}/Baseutils"), + format!("https://bin.pkgforge.dev/{ARCH}/Baseutils"), ), - ("pkg".to_owned(), format!("https://pkg.ajam.dev/{ARCH}")), + ("pkg".to_owned(), format!("https://pkg.pkgforge.dev/{ARCH}")), ]); - let def_config = Self { - soar_path: "$HOME/.soar".to_owned(), + Self { + soar_path: format!("{}/soar", home_data_path()), repositories: vec![Repository { - name: "ajam".to_owned(), - url: format!("https://bin.ajam.dev/{ARCH}"), + name: "pkgforge".to_owned(), + url: format!("https://bin.pkgforge.dev/{ARCH}"), metadata: Some("METADATA.AIO.json".to_owned()), sources, }], parallel: Some(true), - parallel_limit: Some(2), - }; - let serialized = serde_json::to_vec_pretty(&def_config).unwrap(); - fs::write(config_path, &serialized).unwrap(); - serialized - } -} - -impl Default for Config { - fn default() -> Self { - Self::new() + parallel_limit: Some(4), + } } } @@ -110,3 +100,23 @@ pub fn init() { } pub static CONFIG: LazyLock = LazyLock::new(Config::default); + +pub fn generate_default_config() -> Result<()> { + let home_config = home_config_path(); + let config_path = PathBuf::from(home_config).join("soar").join("config.json"); + + if config_path.exists() { + eprintln!("Default config already exists. Not overriding it."); + std::process::exit(1); + } + + fs::create_dir_all(config_path.parent().unwrap())?; + + let def_config = Config::default(); + let serialized = serde_json::to_vec_pretty(&def_config)?; + fs::write(&config_path, &serialized)?; + + println!("Default config is saved at: {}", config_path.display()); + + Ok(()) +} diff --git a/src/core/file.rs b/src/core/file.rs index 7ef918c..d36850a 100644 --- a/src/core/file.rs +++ b/src/core/file.rs @@ -12,7 +12,7 @@ pub enum FileType { pub fn get_file_type(file: &mut BufReader) -> FileType where - T: std::io::Read, + T: Read, { let mut magic_bytes = [0u8; 12]; if file.read_exact(&mut magic_bytes).is_ok() { diff --git a/src/core/util.rs b/src/core/util.rs index 5ead3b5..4662b95 100644 --- a/src/core/util.rs +++ b/src/core/util.rs @@ -1,5 +1,6 @@ use std::{ env, + ffi::CStr, io::Write, mem, path::{Path, PathBuf}, @@ -8,7 +9,7 @@ use std::{ use anyhow::{Context, Result}; use futures::StreamExt; use indicatif::{ProgressState, ProgressStyle}; -use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ}; +use libc::{geteuid, getpwuid, ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ}; use termion::cursor; use tokio::{ fs::{self, File}, @@ -22,9 +23,27 @@ use super::{ constant::{BIN_PATH, CACHE_PATH, INSTALL_TRACK_PATH, PACKAGES_PATH, REGISTRY_PATH}, }; +fn get_username() -> Result { + unsafe { + let uid = geteuid(); + let pwd = getpwuid(uid); + if pwd.is_null() { + anyhow::bail!("Failed to get user"); + } + let username = CStr::from_ptr((*pwd).pw_name) + .to_string_lossy() + .into_owned(); + Ok(username) + } +} + pub fn home_path() -> String { env::var("HOME").unwrap_or_else(|_| { - panic!("Unable to find home directory."); + let username = env::var("USER") + .or_else(|_| env::var("LOGNAME")) + .or_else(|_| get_username().map_err(|_| ())) + .unwrap_or_else(|_| panic!("Couldn't determine username. Please fix the system.")); + format!("home/{}", username) }) } diff --git a/src/lib.rs b/src/lib.rs index 3ff9a53..8f6937d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ use registry::PackageRegistry; use core::{ color::{Color, ColorExt}, - config, + config::{self, generate_default_config}, constant::BIN_PATH, health::check_health, util::{cleanup, setup_required_paths}, @@ -24,7 +24,6 @@ pub async fn init() -> Result<()> { config::init(); setup_required_paths().await?; - let registry = PackageRegistry::new().await?; let path_env = env::var("PATH")?; if !path_env.split(':').any(|p| Path::new(p) == *BIN_PATH) { @@ -35,6 +34,8 @@ pub async fn init() -> Result<()> { ); } + let registry = PackageRegistry::new().await?; + let _ = cleanup().await; match args.command { @@ -109,6 +110,9 @@ pub async fn init() -> Result<()> { Commands::Health => { check_health().await; } + Commands::DefConfig => { + generate_default_config()?; + } }; Ok(()) diff --git a/src/package/install.rs b/src/package/install.rs index 56639a4..c6e64a1 100644 --- a/src/package/install.rs +++ b/src/package/install.rs @@ -1,6 +1,6 @@ use std::{ fs::{File, Permissions}, - io::{BufReader, Write}, + io::BufReader, os::unix::fs::PermissionsExt, path::{Path, PathBuf}, sync::Arc, diff --git a/src/package/mod.rs b/src/package/mod.rs index 916c826..44aab25 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -40,7 +40,7 @@ pub struct Package { pub build_script: String, pub build_log: String, pub category: String, - pub extra_bins: String, + pub provides: String, pub icon: String, pub desktop: Option, pub pkg_id: Option, diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 08a88ad..d703b6a 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -195,7 +195,7 @@ impl PackageRegistry { ), ( "Extra Bins", - package.extra_bins.clone().color(Color::BrightBlack), + package.provides.clone().color(Color::BrightBlack), ), ];