From 041e824fca58e3c2c24f5417e1a7a772ce563746 Mon Sep 17 00:00:00 2001 From: Rabindra Dhakal Date: Fri, 25 Oct 2024 20:47:02 +0545 Subject: [PATCH] refactor(package): reduce hard-coded collections --- Cargo.toml | 4 +- default.nix | 4 ++ src/cli.rs | 16 +++---- src/core/config.rs | 10 ++--- src/core/constant.rs | 7 +-- src/core/util.rs | 36 +++++---------- src/lib.rs | 7 +-- src/registry/fetcher.rs | 14 ++---- src/registry/mod.rs | 8 ++-- src/registry/package/appimage.rs | 22 ++++----- src/registry/package/install.rs | 11 +++-- src/registry/package/run.rs | 2 +- src/registry/storage.rs | 76 ++++++++++++++++++-------------- 13 files changed, 110 insertions(+), 107 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5fcc026..0169417 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "soar" +version = "0.2.0" +authors = ["Rabindra Dhakal "] description = "A modern package manager for Linux" license = "MIT" -version = "0.2.0" edition = "2021" +repository = "https://github.com/QaidVoid/soar" [profile.release] strip = true diff --git a/default.nix b/default.nix index 61f04f6..6d599ab 100644 --- a/default.nix +++ b/default.nix @@ -1,6 +1,10 @@ with import {}; mkShell { nativeBuildInputs = [ + rustc + cargo + clippy + rustfmt rust-analyzer ]; } diff --git a/src/cli.rs b/src/cli.rs index 72c8acb..ee5c108 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -23,23 +23,19 @@ pub enum Commands { packages: Vec, /// Whether to force install the package - #[arg(required = false)] - #[arg(short, long)] + #[arg(required = false, short, long)] force: bool, /// Set portable dir for home & config - #[arg(required = false)] - #[arg(short, long, num_args = 0..=1)] + #[arg(required = false, short, long, num_args = 0..=1)] portable: Option>, /// Set portable home - #[arg(required = false)] - #[arg(long, num_args = 0..=1)] + #[arg(required = false, long, num_args = 0..=1)] portable_home: Option>, /// Set portable config - #[arg(required = false)] - #[arg(long, num_args = 0..=1)] + #[arg(required = false, long, num_args = 0..=1)] portable_config: Option>, }, @@ -50,6 +46,10 @@ pub enum Commands { /// Query to search #[arg(required = true)] query: String, + + /// Case sensitive search + #[arg(required = false, long)] + case_sensitive: bool }, /// Query package info diff --git a/src/core/config.rs b/src/core/config.rs index b734f07..96c058f 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -35,8 +35,8 @@ pub struct Repository { /// `metadata.json` pub registry: Option, - /// Paths for different collections - pub paths: HashMap, + /// Download Sources for different collections + pub sources: HashMap, } impl Repository { @@ -72,7 +72,7 @@ impl Config { } fn generate(config_path: PathBuf) -> Vec { - let paths = HashMap::from([ + let sources = HashMap::from([ ("bin".to_owned(), format!("https://bin.ajam.dev/{ARCH}")), ( "base".to_owned(), @@ -85,9 +85,9 @@ impl Config { soar_path: "$HOME/.soar".to_owned(), repositories: vec![Repository { name: "ajam".to_owned(), - url: "https://bin.ajam.dev".to_owned(), + url: "https://bin.ajam.dev/{ARCH}".to_owned(), registry: Some("METADATA.AIO.json".to_owned()), - paths, + sources, }], parallel: Some(true), parallel_limit: Some(2), diff --git a/src/core/constant.rs b/src/core/constant.rs index dd61d2c..5945f65 100644 --- a/src/core/constant.rs +++ b/src/core/constant.rs @@ -2,6 +2,8 @@ use std::{path::PathBuf, sync::LazyLock}; use super::{config::CONFIG, util::build_path}; +pub static CACHE_PATH: LazyLock = + LazyLock::new(|| build_path(&CONFIG.soar_path).unwrap().join("cache")); pub static REGISTRY_PATH: LazyLock = LazyLock::new(|| build_path(&CONFIG.soar_path).unwrap().join("registry")); pub static BIN_PATH: LazyLock = @@ -11,9 +13,8 @@ pub static INSTALL_TRACK_PATH: LazyLock = pub static PACKAGES_PATH: LazyLock = LazyLock::new(|| build_path(&CONFIG.soar_path).unwrap().join("packages")); -pub const APPIMAGE_MAGIC_BYTES: [u8; 16] = [ - 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x41, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, -]; +pub const APPIMAGE_MAGIC_OFFSET: u64 = 8; +pub const APPIMAGE_MAGIC_BYTES: [u8; 4] = [0x41, 0x49, 0x02, 0x00]; pub const ELF_MAGIC_BYTES: [u8; 4] = [0x7f, 0x45, 0x4c, 0x46]; pub const CAP_SYS_ADMIN: i32 = 21; diff --git a/src/core/util.rs b/src/core/util.rs index 0732d10..64b470b 100644 --- a/src/core/util.rs +++ b/src/core/util.rs @@ -1,9 +1,5 @@ use std::{ - env::{ - self, - consts::{ARCH, OS}, - }, - mem, + env, mem, path::{Path, PathBuf}, }; @@ -17,7 +13,7 @@ use tokio::{ use super::{ color::{Color, ColorExt}, - constant::{BIN_PATH, INSTALL_TRACK_PATH, PACKAGES_PATH}, + constant::{BIN_PATH, CACHE_PATH, INSTALL_TRACK_PATH, PACKAGES_PATH}, }; pub fn home_path() -> String { @@ -73,14 +69,6 @@ pub fn build_path(path: &str) -> Result { Ok(PathBuf::from(result)) } -/// Retrieves the platform string in the format `ARCH-Os`. -/// -/// This function combines the architecture (e.g., `x86_64`) and the operating -/// system (e.g., `Linux`) into a single string to identify the platform. -pub fn get_platform() -> String { - format!("{ARCH}-{}{}", &OS[..1].to_uppercase(), &OS[1..]) -} - pub fn format_bytes(bytes: u64) -> String { let kb = 1024u64; let mb = kb * 1024; @@ -211,19 +199,19 @@ pub async fn download(url: &str, what: &str, silent: bool) -> Result> { } pub async fn cleanup() -> Result<()> { - let mut cache_dir = home_cache_path(); - cache_dir.push_str("/soar"); - let cache_dir = build_path(&cache_dir)?; + let mut tree = fs::read_dir(&*CACHE_PATH).await?; - if cache_dir.exists() { - let mut tree = fs::read_dir(&cache_dir).await?; + while let Some(entry) = tree.next_entry().await? { + let path = entry.path(); + if xattr::get(&path, "user.managed_by")?.as_deref() != Some(b"soar") { + continue; + }; - while let Some(entry) = tree.next_entry().await? { - let path = entry.path(); - if xattr::get(&path, "user.managed_by")?.as_deref() != Some(b"soar") { - continue; - }; + let modified_at = path.metadata()?.modified()?; + let elapsed = modified_at.elapsed()?.as_secs(); + let cache_ttl = 28800u64; + if cache_ttl.saturating_sub(elapsed) == 0 { fs::remove_file(path).await?; } } diff --git a/src/lib.rs b/src/lib.rs index 2b1fa95..2a5194b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,8 @@ pub async fn init() -> Result<()> { ); } + let _ = cleanup().await; + match args.command { Commands::Install { packages, @@ -62,7 +64,6 @@ pub async fn init() -> Result<()> { .await?; } Commands::Sync => { - cleanup().await?; let mut registry = registry; registry.fetch().await?; } @@ -75,8 +76,8 @@ pub async fn init() -> Result<()> { Commands::ListInstalledPackages { packages } => { registry.info(packages.as_deref()).await?; } - Commands::Search { query } => { - registry.search(&query).await?; + Commands::Search { query, case_sensitive} => { + registry.search(&query, case_sensitive).await?; } Commands::Query { query } => { registry.query(&query).await?; diff --git a/src/registry/fetcher.rs b/src/registry/fetcher.rs index fa0656a..77659a1 100644 --- a/src/registry/fetcher.rs +++ b/src/registry/fetcher.rs @@ -7,7 +7,7 @@ use tokio::fs; use crate::core::{ color::{Color, ColorExt}, config::Repository, - util::{download, get_platform}, + util::download, }; use super::package::Package; @@ -26,11 +26,9 @@ impl RegistryFetcher { } pub async fn execute(&self, repository: &Repository) -> Result> { - let platform = get_platform(); let url = format!( - "{}/{}/{}", + "{}/{}", repository.url, - platform, repository .registry .to_owned() @@ -55,9 +53,7 @@ impl RegistryFetcher { .rev() .nth(1) .map(|v| v.to_owned()) - .filter(|v| { - v != ARCH && v != &platform && v != &platform.replace('-', "_") - }), + .filter(|v| v != ARCH), ..package.clone() }); acc @@ -88,11 +84,9 @@ impl RegistryFetcher { } pub async fn checksum(&self, repository: &Repository) -> Result> { - let platform = get_platform(); let url = format!( - "{}/{}/{}", + "{}/{}", repository.url, - platform, repository .registry .to_owned() diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 0797570..c6175de 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -89,7 +89,7 @@ impl PackageRegistry { // fetch default icons let icon_futures: Vec<_> = repo - .paths + .sources .iter() .map(|(key, base_url)| { let base_url = format!("{}/{}.default.png", base_url, key); @@ -98,7 +98,7 @@ impl PackageRegistry { .collect(); let icons = try_join_all(icon_futures).await?; - for (key, icon) in repo.paths.keys().zip(icons) { + for (key, icon) in repo.sources.keys().zip(icons) { let icon_path = REGISTRY_PATH .join("icons") .join(format!("{}-{}.png", repo.name, key)); @@ -143,9 +143,9 @@ impl PackageRegistry { self.storage.remove_packages(package_names).await } - pub async fn search(&self, package_name: &str) -> Result<()> { + pub async fn search(&self, package_name: &str, case_sensitive: bool) -> Result<()> { let installed_guard = self.installed_packages.lock().await; - let result = self.storage.search(package_name).await; + let result = self.storage.search(package_name, case_sensitive).await; if result.is_empty() { Err(anyhow::anyhow!("No packages found")) diff --git a/src/registry/package/appimage.rs b/src/registry/package/appimage.rs index 219e88e..d4a46a1 100644 --- a/src/registry/package/appimage.rs +++ b/src/registry/package/appimage.rs @@ -1,7 +1,7 @@ use std::{ collections::HashSet, fs::File, - io::{BufReader, BufWriter, Read, Seek, Write}, + io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write}, path::{Path, PathBuf}, }; @@ -13,7 +13,7 @@ use tokio::{fs, try_join}; use crate::{ core::{ color::{Color, ColorExt}, - constant::{APPIMAGE_MAGIC_BYTES, BIN_PATH, PACKAGES_PATH}, + constant::{APPIMAGE_MAGIC_BYTES, APPIMAGE_MAGIC_OFFSET, BIN_PATH, PACKAGES_PATH}, util::{download, home_data_path}, }, error, info, @@ -79,9 +79,11 @@ fn normalize_image(image: DynamicImage) -> DynamicImage { } fn is_appimage(file: &mut BufReader) -> bool { - let mut magic_bytes = [0_u8; 16]; - if file.read_exact(&mut magic_bytes).is_ok() { - return APPIMAGE_MAGIC_BYTES == magic_bytes; + if file.seek(SeekFrom::Start(APPIMAGE_MAGIC_OFFSET)).is_ok() { + let mut magic_bytes = [0_u8; 4]; + if file.read_exact(&mut magic_bytes).is_ok() { + return APPIMAGE_MAGIC_BYTES == magic_bytes; + } } false } @@ -144,12 +146,11 @@ pub async fn remove_applinks(name: &str, bin_name: &str, file_path: &Path) -> Re Ok(()) } -pub async fn extract_appimage(package: &Package, file_path: &Path) -> Result<()> { +pub async fn integrate_appimage(package: &Package, file_path: &Path) -> Result { let mut file = BufReader::new(File::open(file_path)?); if !is_appimage(&mut file) { - use_remote_files(package, file_path).await?; - return Ok(()); + return Ok(false); } let offset = find_offset(&mut file).await?; @@ -183,7 +184,7 @@ pub async fn extract_appimage(package: &Package, file_path: &Path) -> Result<()> } } - Ok(()) + Ok(true) } fn resolve_and_extract( @@ -300,7 +301,8 @@ async fn process_desktop( Ok(()) } -pub async fn use_remote_files(package: &Package, file_path: &Path) -> Result<()> { +// TODO: use this if the package don't contain the required files +pub async fn _use_remote_files(package: &Package, file_path: &Path) -> Result<()> { let home_data = home_data_path(); let data_path = Path::new(&home_data); diff --git a/src/registry/package/install.rs b/src/registry/package/install.rs index fd6898d..e848811 100644 --- a/src/registry/package/install.rs +++ b/src/registry/package/install.rs @@ -13,7 +13,7 @@ use crate::{ error, registry::{ installed::InstalledPackages, - package::appimage::{extract_appimage, setup_portable_dir}, + package::appimage::{integrate_appimage, setup_portable_dir}, }, warn, }; @@ -64,8 +64,7 @@ impl Installer { ); if !force && is_installed { - error!("{}: Package is already installed", prefix); - return Err(anyhow::anyhow!("")); + return Err(anyhow::anyhow!("{}: Package is already installed", prefix)); } if is_installed && !is_update { @@ -166,9 +165,9 @@ impl Installer { self.save_file().await?; self.symlink_bin(&installed_packages).await?; - // TODO: use magic bytes instead - if self.resolved_package.collection == "pkg" { - extract_appimage(package, &self.install_path).await?; + + let ai_integrated = integrate_appimage(package, &self.install_path).await?; + if ai_integrated { setup_portable_dir( &package.bin_name, &self.install_path, diff --git a/src/registry/package/run.rs b/src/registry/package/run.rs index a855f4e..4546fb3 100644 --- a/src/registry/package/run.rs +++ b/src/registry/package/run.rs @@ -116,7 +116,7 @@ impl Runner { let result = validate_checksum(&package.bsum, &self.temp_path).await; if result.is_err() { error!( - "\n{}: Checksum verification failed.", + "{}: Checksum verification failed.", package_name.color(Color::Blue) ); } diff --git a/src/registry/storage.rs b/src/registry/storage.rs index b424ebb..c369958 100644 --- a/src/registry/storage.rs +++ b/src/registry/storage.rs @@ -19,14 +19,15 @@ use crate::{ core::{ color::{Color, ColorExt}, config::CONFIG, - util::{build_path, format_bytes, get_platform, home_cache_path}, + constant::CACHE_PATH, + util::format_bytes, }, error, registry::{ installed::InstalledPackages, package::{parse_package_query, ResolvedPackage}, }, - success, warn, + warn, }; use super::{ @@ -157,7 +158,7 @@ impl PackageStorage { }; } } - success!( + println!( "Installed {}/{} packages", installed_count.load(Ordering::Relaxed).color(Color::Blue), resolved_packages.len().color(Color::BrightBlue) @@ -239,9 +240,13 @@ impl PackageStorage { } } - pub async fn search(&self, query: &str) -> Vec { + pub async fn search(&self, query: &str, case_sensitive: bool) -> Vec { let query = parse_package_query(query); - let pkg_name = query.name.trim(); + let pkg_name = if case_sensitive { + query.name.trim().to_owned() + } else { + query.name.trim().to_lowercase() + }; let mut resolved_packages: Vec<(u32, Package, String, String)> = Vec::new(); for (repo_name, packages) in &self.repository { @@ -251,9 +256,15 @@ impl PackageStorage { .flat_map(|(_, packages)| { packages.iter().filter_map(|pkg| { let mut score = 0; - if pkg.name == pkg_name { + let found_pkg_name = if case_sensitive { + pkg.name.clone() + } else { + pkg.name.to_lowercase() + }; + + if found_pkg_name == pkg_name { score += 2; - } else if pkg.name.contains(pkg_name) { + } else if found_pkg_name.contains(&pkg_name) { score += 1; } else { return None; @@ -342,11 +353,7 @@ impl PackageStorage { } pub async fn run(&self, command: &[String]) -> Result<()> { - let mut cache_dir = home_cache_path(); - cache_dir.push_str("/soar"); - let cache_dir = build_path(&cache_dir)?; - - fs::create_dir_all(&cache_dir).await?; + fs::create_dir_all(&*CACHE_PATH).await?; let package_name = &command[0]; let args = if command.len() > 1 { @@ -355,32 +362,37 @@ impl PackageStorage { &[] }; let runner = if let Ok(resolved_pkg) = self.resolve_package(package_name) { - let package_path = cache_dir.join(&resolved_pkg.package.bin_name); + let package_path = CACHE_PATH.join(&resolved_pkg.package.bin_name); Runner::new(&resolved_pkg, package_path, args) } else { let query = parse_package_query(package_name); - let package_path = cache_dir.join(&query.name); + let package_path = CACHE_PATH.join(&query.name); let mut resolved_pkg = ResolvedPackage::default(); resolved_pkg.package.name = query.name; resolved_pkg.package.variant = query.variant; - resolved_pkg.collection = query.collection.unwrap_or_default(); - - // TODO: don't use just first repo - let platform = get_platform(); - let repo = &CONFIG.repositories[0]; - let base_url = format!("{}/{}", repo.url, platform); - - let collection = if resolved_pkg.collection == "base" { - "/Baseutils" - } else { - "" - }; - let download_url = format!( - "{}{}/{}", - base_url, - collection, - resolved_pkg.package.full_name('/') - ); + + // TODO: check all the repo for package instead of choosing the first + let base_url = CONFIG + .repositories + .iter() + .find_map(|repo| { + if let Some(collection) = &query.collection { + repo.sources.get(collection).cloned() + } else { + repo.sources.values().next().cloned() + } + }) + .ok_or_else(|| anyhow::anyhow!("No repository found for the package"))?; + + resolved_pkg.collection = query.collection.unwrap_or_else(|| { + CONFIG + .repositories + .iter() + .find_map(|repo| repo.sources.keys().next().cloned()) + .unwrap_or_default() + }); + + let download_url = format!("{}/{}", base_url, resolved_pkg.package.full_name('/')); resolved_pkg.package.download_url = download_url; Runner::new(&resolved_pkg, package_path, args) };