From 424b0e35eb36a4ef3779bb4c69c054f4137130a4 Mon Sep 17 00:00:00 2001 From: Rabindra Dhakal Date: Sat, 9 Nov 2024 14:33:02 +0545 Subject: [PATCH] feat(log): add tracing, verbosity, json output --- Cargo.lock | 98 ++++++++++++++++++++++++++++ Cargo.toml | 2 + src/cli.rs | 11 +++- src/core/health.rs | 26 ++++---- src/core/log.rs | 132 +++++++++++++++++++++++++------------- src/core/util.rs | 9 +-- src/lib.rs | 43 +++++++++---- src/main.rs | 5 +- src/misc/download.rs | 9 ++- src/package/appimage.rs | 19 ++---- src/package/install.rs | 123 ++++++++++++++++++++++------------- src/package/mod.rs | 2 +- src/package/remove.rs | 4 +- src/package/run.rs | 14 ++-- src/package/update.rs | 31 ++++++--- src/registry/installed.rs | 10 +-- src/registry/loader.rs | 11 +--- src/registry/mod.rs | 26 ++++---- src/registry/storage.rs | 47 ++++++++++---- 19 files changed, 423 insertions(+), 199 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07a7f74..f157415 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -875,6 +884,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1138,6 +1156,50 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "reqwest" version = "0.12.8" @@ -1348,6 +1410,15 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1397,6 +1468,8 @@ dependencies = [ "strip-ansi-escapes", "termion", "tokio", + "tracing", + "tracing-subscriber", "which", "xattr", ] @@ -1496,6 +1569,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -1616,6 +1699,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + [[package]] name = "try-lock" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index 4402d32..eb6560d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,5 +40,7 @@ serde_json = "1.0.128" strip-ansi-escapes = "0.2.0" termion = "4.0.3" tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] } +tracing = { version = "0.1.40", default-features = false } +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "fmt"], default-features = false } which = "6.0.3" xattr = { version = "1.3.1", default-features = false } diff --git a/src/cli.rs b/src/cli.rs index f2c33bf..96a1244 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,4 @@ -use clap::{Parser, Subcommand}; +use clap::{ArgAction, Parser, Subcommand}; #[derive(Parser)] #[command( @@ -13,9 +13,14 @@ use clap::{Parser, Subcommand}; arg_required_else_help = true )] pub struct Args { - /// Unimplemented + #[arg(short = 'v', long, action = ArgAction::Count)] + pub verbose: u8, + + #[arg(short, long)] + pub quiet: bool, + #[arg(short, long)] - pub verbose: bool, + pub json: bool, #[clap(subcommand)] pub command: Commands, diff --git a/src/core/health.rs b/src/core/health.rs index e4ccc20..3f84f7f 100644 --- a/src/core/health.rs +++ b/src/core/health.rs @@ -3,13 +3,11 @@ use std::{cmp::Ordering, future::Future, os::unix::fs::PermissionsExt, path::Pat use futures::future::join_all; use libc::{fork, unshare, waitpid, CLONE_NEWUSER, PR_CAPBSET_READ}; use tokio::fs; +use tracing::{info, warn}; -use crate::{ - core::{ - color::{Color, ColorExt}, - constant::CAP_MKNOD, - }, - successln, warnln, +use crate::core::{ + color::{Color, ColorExt}, + constant::CAP_MKNOD, }; use super::constant::CAP_SYS_ADMIN; @@ -49,7 +47,7 @@ pub async fn check_health() { Box::pin(check_capabilities()), ]; - println!("{0} FUSE CHECK {0}", "☵".repeat(4)); + info!("{0} FUSE CHECK {0}", "☵".repeat(4)); check_fusermount().await; let results = join_all(checks).await; @@ -60,17 +58,15 @@ pub async fn check_health() { } }); - println!(); - - println!("{0} USER NAMESPACE CHECK {0}", "☵".repeat(4)); + info!("\n{0} USER NAMESPACE CHECK {0}", "☵".repeat(4)); for error in &errors { - warnln!("{}", error); + warn!("{}", error); } if errors.is_empty() { - successln!("User namespace checked successfully.") + info!("User namespace checked successfully.") } else { - println!( + info!( "{} {}", "More info at:".color(Color::Cyan), "https://l.ajam.dev/namespace".color(Color::Blue) @@ -168,13 +164,13 @@ async fn check_fusermount() { } if !error.is_empty() { - warnln!( + warn!( "{}\n{} {}", error, "More info at:".color(Color::Cyan), "https://l.ajam.dev/fuse".color(Color::Blue) ); } else { - successln!("Fuse checked successfully."); + info!("Fuse checked successfully."); } } diff --git a/src/core/log.rs b/src/core/log.rs index 2ea6da2..f7e2a18 100644 --- a/src/core/log.rs +++ b/src/core/log.rs @@ -1,55 +1,101 @@ -#[macro_export] -macro_rules! warnln { - ($($arg:tt)*) => { - println!("{} {}", "[WARN]".color(Color::BrightYellow).bold(), format!($($arg)*)) - }; -} +use tracing::{Event, Level, Subscriber}; +use tracing_subscriber::{ + fmt::{ + self, + format::{FmtSpan, Writer}, + FmtContext, FormatEvent, FormatFields, + }, + registry::LookupSpan, +}; -#[macro_export] -macro_rules! infoln { - ($($arg:tt)*) => { - println!("{} {}", "[INFO]".color(Color::BrightBlue).bold(), format!($($arg)*)) - }; -} +use crate::cli::Args; -#[macro_export] -macro_rules! errorln { - ($($arg:tt)*) => { - eprintln!("{} {}", "[ERROR]".color(Color::BrightRed).bold(), format!($($arg)*)) - }; -} +use super::color::{Color, ColorExt}; -#[macro_export] -macro_rules! successln { - ($($arg:tt)*) => { - println!("{} {}", "[SUCCESS]".color(Color::BrightGreen).bold(), format!($($arg)*)) - }; -} +pub struct CustomFormatter; -#[macro_export] -macro_rules! warn { - ($($arg:tt)*) => { - print!("{} {}", "[WARN]".color(Color::BrightYellow).bold(), format!($($arg)*)) - }; +impl FormatEvent for CustomFormatter +where + S: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + fn format_event( + &self, + ctx: &FmtContext<'_, S, N>, + mut writer: Writer<'_>, + event: &Event<'_>, + ) -> std::fmt::Result { + match *event.metadata().level() { + Level::TRACE => write!(writer, "{} ", "[TRACE]".color(Color::BrightMagenta)), + Level::DEBUG => write!(writer, "{} ", "[DEBUG]".color(Color::BrightBlue)), + Level::INFO => write!(writer, ""), + Level::WARN => write!(writer, "{} ", "[WARN]".color(Color::BrightYellow)), + Level::ERROR => write!(writer, "{} ", "[ERROR]".color(Color::BrightRed)), + }?; + + ctx.field_format().format_fields(writer.by_ref(), event)?; + + writeln!(writer) + } } -#[macro_export] -macro_rules! info { - ($($arg:tt)*) => { - print!("{} {}", "[INFO]".color(Color::BrightBlue).bold(), format!($($arg)*)) - }; +pub struct CleanJsonFormatter; + +impl FormatEvent for CleanJsonFormatter +where + S: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + fn format_event( + &self, + ctx: &FmtContext<'_, S, N>, + mut writer: Writer<'_>, + event: &Event<'_>, + ) -> std::fmt::Result { + let mut fields_output = String::new(); + + let temp_writer = Writer::new(&mut fields_output); + ctx.field_format().format_fields(temp_writer, event)?; + let clean_fields = strip_ansi_escapes::strip_str(&fields_output); + + let json = serde_json::json!({ + "level": event.metadata().level().to_string(), + "fields": clean_fields, + "target": event.metadata().target(), + }); + + writeln!(writer, "{}", json) + } } -#[macro_export] -macro_rules! error { - ($($arg:tt)*) => { - eprint!("{} {}", "[ERROR]".color(Color::BrightRed).bold(), format!($($arg)*)) +pub fn setup_logging(args: &Args) { + let filter_level = if args.quiet { + Level::ERROR + } else if args.verbose >= 2 { + Level::TRACE + } else if args.verbose == 1 { + Level::DEBUG + } else { + Level::INFO }; -} -#[macro_export] -macro_rules! success { - ($($arg:tt)*) => { - print!("{} {}", "[SUCCESS]".color(Color::BrightGreen).bold(), format!($($arg)*)) + let builder = fmt::Subscriber::builder() + .with_env_filter(format!("soar_cli={}", filter_level)) + .with_target(false) + .with_thread_ids(false) + .with_thread_names(false) + .with_file(false) + .with_line_number(false) + .with_span_events(FmtSpan::NONE) + .with_writer(std::io::stderr) + .compact() + .without_time(); + + let subscriber: Box = if args.json { + Box::new(builder.event_format(CleanJsonFormatter).finish()) + } else { + Box::new(builder.event_format(CustomFormatter).finish()) }; + + tracing::subscriber::set_global_default(subscriber).expect("Failed to set tracing subscriber"); } diff --git a/src/core/util.rs b/src/core/util.rs index e711130..17ab73f 100644 --- a/src/core/util.rs +++ b/src/core/util.rs @@ -15,6 +15,7 @@ use tokio::{ fs::{self, File}, io::{AsyncReadExt, AsyncWriteExt}, }; +use tracing::info; use crate::{core::constant::ROOT_PATH, warn}; @@ -217,7 +218,7 @@ pub async fn download(url: &str, what: &str, silent: bool) -> Result> { let mut content = Vec::new(); if !silent { - println!( + info!( "Fetching {} from {} [{}]", what.color(Color::Cyan), url.color(Color::Blue), @@ -412,7 +413,7 @@ pub fn print_env() { .then(|| CACHE_PATH.read_link().unwrap()) .unwrap_or(CACHE_PATH.to_path_buf()); - println!("SOAR_ROOT={}", root_path.display()); - println!("SOAR_BIN={}", bin_path.display()); - println!("SOAR_CACHE={}", cache_path.display()); + info!("SOAR_ROOT={}", root_path.display()); + info!("SOAR_BIN={}", bin_path.display()); + info!("SOAR_CACHE={}", cache_path.display()); } diff --git a/src/lib.rs b/src/lib.rs index 2fd7ede..925c67c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,12 +3,14 @@ use clap::Parser; use cli::{Args, Commands}; use misc::download::download_and_save; use registry::PackageRegistry; +use tracing::{debug, error, trace, warn}; use core::{ color::{Color, ColorExt}, config::{self, generate_default_config}, constant::BIN_PATH, health::check_health, + log::setup_logging, util::{cleanup, print_env, setup_required_paths}, }; use std::{env, path::Path}; @@ -22,20 +24,27 @@ mod registry; pub async fn init() -> Result<()> { let args = Args::parse(); + setup_logging(&args); + + debug!("Initializing soar"); config::init(); + + debug!("Setting up paths"); setup_required_paths().await?; let path_env = env::var("PATH")?; if !path_env.split(':').any(|p| Path::new(p) == *BIN_PATH) { - warnln!( + warn!( "{} is not in {1}. Please add it to {1} to use installed binaries.", &*BIN_PATH.to_string_lossy().color(Color::Blue), "PATH".color(Color::BrightGreen).bold() ); } - let registry = PackageRegistry::new().await?; + debug!("Initializing package registry"); + let registry = PackageRegistry::new(); + trace!("Running cleanup"); let _ = cleanup().await; match args.command { @@ -57,6 +66,7 @@ pub async fn init() -> Result<()> { let portable_config = portable_config.map(|p| p.unwrap_or_default()); registry + .await? .install_packages( &packages, force, @@ -64,6 +74,7 @@ pub async fn init() -> Result<()> { portable_home, portable_config, yes, + args.quiet, ) .await?; } @@ -72,41 +83,47 @@ pub async fn init() -> Result<()> { // it can be used to force sync without doing any other operation } Commands::Remove { packages, exact } => { - registry.remove_packages(&packages, exact).await?; + registry.await?.remove_packages(&packages, exact).await?; } Commands::Update { packages } => { - registry.update(packages.as_deref()).await?; + registry + .await? + .update(packages.as_deref(), args.quiet) + .await?; } Commands::ListInstalledPackages { packages } => { - registry.info(packages.as_deref()).await?; + registry.await?.info(packages.as_deref()).await?; } Commands::Search { query, case_sensitive, limit, } => { - registry.search(&query, case_sensitive, limit).await?; + registry + .await? + .search(&query, case_sensitive, limit) + .await?; } Commands::Query { query } => { - registry.query(&query).await?; + registry.await?.query(&query).await?; } Commands::ListPackages { collection } => { - registry.list(collection.as_deref()).await?; + registry.await?.list(collection.as_deref()).await?; } Commands::Inspect { package } => { - registry.inspect(&package, "script").await?; + registry.await?.inspect(&package, "script").await?; } Commands::Log { package } => { - registry.inspect(&package, "log").await?; + registry.await?.inspect(&package, "log").await?; } Commands::Run { command, yes } => { - registry.run(command.as_ref(), yes).await?; + registry.await?.run(command.as_ref(), yes).await?; } Commands::Use { package } => { - registry.use_package(&package).await?; + registry.await?.use_package(&package, args.quiet).await?; } Commands::Download { links, yes, output } => { - download_and_save(registry, links.as_ref(), yes, output).await?; + download_and_save(registry.await?, links.as_ref(), yes, output).await?; } Commands::Health => { check_health().await; diff --git a/src/main.rs b/src/main.rs index 70479e5..9e171ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![allow(clippy::needless_return)] -use soar_cli::{core::color::{Color, ColorExt}, errorln, init}; +use soar_cli::init; +use tracing::error; #[tokio::main] async fn main() { @@ -9,6 +10,6 @@ async fn main() { } if let Err(e) = init().await { - errorln!("{}", e); + error!("{}", e); } } diff --git a/src/misc/download.rs b/src/misc/download.rs index 3d467e1..3452670 100644 --- a/src/misc/download.rs +++ b/src/misc/download.rs @@ -9,6 +9,7 @@ use tokio::{ fs::{self, File}, io::{AsyncReadExt, AsyncWriteExt, BufReader}, }; +use tracing::{error, info}; use crate::{ core::{ @@ -16,10 +17,8 @@ use crate::{ constant::ELF_MAGIC_BYTES, util::{download_progress_style, format_bytes}, }, - error, package::parse_package_query, registry::{select_single_package, PackageRegistry}, - successln, }; fn extract_filename(url: &str) -> String { @@ -80,7 +79,7 @@ async fn download(url: &str, output: Option) -> Result<()> { let temp_path = format!("{}.tmp", output_path.display()); - println!( + info!( "Downloading file from {} [{}]", url.color(Color::Blue), format_bytes(response.content_length().unwrap_or_default()).color(Color::Yellow) @@ -117,7 +116,7 @@ async fn download(url: &str, output: Option) -> Result<()> { fs::set_permissions(&output_path, Permissions::from_mode(0o755)).await?; } - successln!("Downloaded {}", output_path.display().color(Color::Blue)); + info!("Downloaded {}", output_path.display().color(Color::Blue)); Ok(()) } @@ -133,7 +132,7 @@ pub async fn download_and_save( download(url.as_str(), output.clone()).await?; } else { error!("{} is not a valid URL", link.color(Color::Blue)); - println!("Searching for package instead.."); + info!("Searching for package instead.."); let query = parse_package_query(link); let packages = registry.storage.get_packages(&query); diff --git a/src/package/appimage.rs b/src/package/appimage.rs index 158e8d9..d875974 100644 --- a/src/package/appimage.rs +++ b/src/package/appimage.rs @@ -9,14 +9,12 @@ use anyhow::{Context, Result}; use backhand::{kind::Kind, FilesystemReader, InnerNode, Node, SquashfsFileReader}; use image::{imageops::FilterType, DynamicImage, GenericImageView}; use tokio::{fs, try_join}; +use tracing::{error, info}; -use crate::{ - core::{ - color::{Color, ColorExt}, - constant::{BIN_PATH, PACKAGES_PATH}, - util::{download, home_data_path}, - }, - error, infoln, +use crate::core::{ + color::{Color, ColorExt}, + constant::{BIN_PATH, PACKAGES_PATH}, + util::{download, home_data_path}, }; use super::Package; @@ -68,12 +66,9 @@ fn normalize_image(image: DynamicImage) -> DynamicImage { let (new_width, new_height) = find_nearest_supported_dimension(width, height); if (width, height) != (new_width, new_height) { - infoln!( + info!( "Resizing image from {}x{} to {}x{}", - width, - height, - new_width, - new_height + width, height, new_width, new_height ); image.resize(new_width, new_height, FilterType::Lanczos3) } else { diff --git a/src/package/install.rs b/src/package/install.rs index 43bd5e7..bcb06c3 100644 --- a/src/package/install.rs +++ b/src/package/install.rs @@ -15,6 +15,7 @@ use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, sync::Mutex, }; +use tracing::info; use crate::{ core::{ @@ -58,7 +59,7 @@ impl Installer { portable: Option, portable_home: Option, portable_config: Option, - multi_progress: Arc, + multi_progress: Option>, ) -> Result<()> { let package = &self.resolved_package.package; @@ -102,8 +103,13 @@ 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()); + let warn_bar = if let Some(ref mp) = multi_progress { + let warn_bar = mp.insert(1, ProgressBar::new(0)); + warn_bar.set_style(ProgressStyle::default_bar().template("{msg}").unwrap()); + Some(warn_bar) + } else { + None + }; match file_type { FileType::AppImage => { if integrate_appimage(&mut file, package, &self.install_path) @@ -118,8 +124,8 @@ impl Installer { portable_config, ) .await?; - } else { - warn.finish_with_message(format!( + } else if let Some(wb) = warn_bar { + wb.finish_with_message(format!( "{}: {}", prefix, "Failed to integrate AppImage".color(Color::BrightYellow) @@ -139,8 +145,8 @@ impl Installer { portable_config, ) .await?; - } else { - warn.finish_with_message(format!( + } else if let Some(wb) = warn_bar { + wb.finish_with_message(format!( "{}: {}", prefix, "Failed to integrate FlatImage".color(Color::BrightYellow) @@ -157,17 +163,19 @@ impl Installer { .await?; } - let installed_progress = multi_progress.insert_from_back(1, ProgressBar::new(0)); - installed_progress.set_style(ProgressStyle::default_bar().template("{msg}").unwrap()); - installed_progress.finish_with_message(format!( - "[{}/{}] Installed {}", - (idx + 1).color(Color::Green), - total.color(Color::Cyan), - package.full_name('/').color(Color::Blue) - )); + if let Some(mp) = multi_progress { + let installed_progress = mp.insert_from_back(1, ProgressBar::new(0)); + installed_progress.set_style(ProgressStyle::default_bar().template("{msg}").unwrap()); + installed_progress.finish_with_message(format!( + "[{}/{}] Installed {}", + (idx + 1).color(Color::Green), + total.color(Color::Cyan), + package.full_name('/').color(Color::Blue) + )); + } if !package.note.is_empty() { - println!( + info!( "{}: {}", prefix, package @@ -182,7 +190,7 @@ impl Installer { async fn download_remote_package( &self, - multi_progress: Arc, + multi_progress: Option>, prefix: &str, ) -> Result<()> { let prefix = prefix.to_owned(); @@ -207,11 +215,17 @@ impl Installer { .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)); + let download_progress = if let Some(ref mp) = multi_progress { + let download_progress = mp.insert_from_back(1, ProgressBar::new(0)); - download_progress.set_length(total_size); - download_progress.set_message(prefix.clone()); + download_progress.set_style(download_progress_style(true)); + + download_progress.set_length(total_size); + download_progress.set_message(prefix.clone()); + Some(download_progress) + } else { + None + }; if !response.status().is_success() { return Err(anyhow::anyhow!( @@ -232,28 +246,42 @@ impl Installer { 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); + if let Some(ref pb) = download_progress { + pb.inc(chunk.len() as u64); + } + } + if let Some(ref pb) = download_progress { + pb.finish(); } - 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) - )); + let warn_bar = if let Some(mp) = multi_progress { + let warn_bar = mp.insert_from_back(1, ProgressBar::new(0)); + warn_bar.set_style(ProgressStyle::default_bar().template("{msg}").unwrap()); + Some(warn_bar) } else { - let result = validate_checksum(&package.bsum, &self.temp_path).await; - if result.is_err() { - warn_bar.finish_with_message(format!( + None + }; + if package.bsum == "null" { + if let Some(wb) = warn_bar { + wb.finish_with_message(format!( "{}: {}", prefix, - "Checksum verification failed. Installing anyway.".color(Color::BrightYellow) + "Missing checksum. Installing anyway.".color(Color::BrightYellow) )); } + } else { + let result = validate_checksum(&package.bsum, &self.temp_path).await; + if result.is_err() { + if let Some(wb) = warn_bar { + wb.finish_with_message(format!( + "{}: {}", + prefix, + "Checksum verification failed. Installing anyway." + .color(Color::BrightYellow) + )); + } + } } Ok(()) @@ -261,19 +289,24 @@ impl Installer { async fn copy_local_package( &self, - multi_progress: Arc, + multi_progress: Option>, 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 download_progress = if let Some(mp) = multi_progress { + let download_progress = mp.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 total_size = package.size.parse::().unwrap_or_default(); + download_progress.set_length(total_size); + download_progress.set_message(prefix.clone()); + Some(download_progress) + } else { + None + }; let mut file = fs::OpenOptions::new() .create(true) @@ -290,9 +323,13 @@ impl Installer { } file.write_all(&buffer[..n]).await?; - download_progress.inc(n as u64); + if let Some(ref pb) = download_progress { + pb.inc(n as u64); + } + } + if let Some(pb) = download_progress { + pb.finish(); } - download_progress.finish(); file.flush().await?; Ok(()) diff --git a/src/package/mod.rs b/src/package/mod.rs index 00e7dbb..deb7738 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -57,7 +57,7 @@ impl ResolvedPackage { portable: Option, portable_home: Option, portable_config: Option, - multi_progress: Arc, + multi_progress: Option>, ) -> Result<()> { let mut installer = Installer::new(self); installer diff --git a/src/package/remove.rs b/src/package/remove.rs index 6360952..1422c39 100644 --- a/src/package/remove.rs +++ b/src/package/remove.rs @@ -2,6 +2,7 @@ use std::path::Path; use anyhow::{Context, Result}; use tokio::fs; +use tracing::info; use crate::{ core::{ @@ -10,7 +11,6 @@ use crate::{ }, package::appimage::remove_applinks, registry::installed::{InstalledPackage, InstalledPackages}, - successln, }; pub struct Remover { @@ -34,7 +34,7 @@ impl Remover { self.remove_package_path(&install_dir).await?; installed_packages.unregister_package(&self.package).await?; - successln!( + info!( "Package {} removed successfully.", package.full_name('/').color(Color::Blue) ); diff --git a/src/package/run.rs b/src/package/run.rs index 23e4077..e9f9935 100644 --- a/src/package/run.rs +++ b/src/package/run.rs @@ -3,13 +3,11 @@ use std::{fs::Permissions, os::unix::fs::PermissionsExt, path::PathBuf, process: use anyhow::{Context, Result}; use futures::StreamExt; use tokio::{fs, io::AsyncWriteExt}; +use tracing::{error, info, warn}; -use crate::{ - core::{ - color::{Color, ColorExt}, - util::{format_bytes, validate_checksum}, - }, - error, infoln, warnln, +use crate::core::{ + color::{Color, ColorExt}, + util::{format_bytes, validate_checksum}, }; use super::ResolvedPackage; @@ -43,7 +41,7 @@ impl Runner { self.install_path.to_string_lossy().color(Color::Blue) )); } else { - infoln!( + info!( "Found existing cache for {}", package_name.color(Color::Blue) ); @@ -108,7 +106,7 @@ impl Runner { } if package.bsum == "null" { - warnln!( + warn!( "Missing checksum for {}. Installing anyway.", package.full_name('/').color(Color::Blue) ); diff --git a/src/package/update.rs b/src/package/update.rs index f0ae4da..58b05fa 100644 --- a/src/package/update.rs +++ b/src/package/update.rs @@ -2,12 +2,12 @@ use std::sync::Arc; use anyhow::Result; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use tracing::info; use crate::{ core::color::{Color, ColorExt}, error, registry::PackageRegistry, - successln, }; use super::{parse_package_query, PackageQuery, ResolvedPackage}; @@ -23,7 +23,7 @@ impl Updater { } } - pub async fn execute(&self, registry: &PackageRegistry) -> Result<()> { + pub async fn execute(&self, registry: &PackageRegistry, quiet: bool) -> Result<()> { let installed_guard = registry.installed_packages.lock().await; let packages = match &self.package_names { Some(r) => { @@ -72,10 +72,15 @@ impl Updater { drop(installed_guard); - let total_progress_bar = - multi_progress.add(ProgressBar::new(packages_to_update.len() as u64)); + let total_progress_bar = if !quiet { + Some(multi_progress.add(ProgressBar::new(packages_to_update.len() as u64))) + } else { + None + }; - total_progress_bar.set_style(ProgressStyle::with_template("Updating {pos}/{len}").unwrap()); + if let Some(pb) = &total_progress_bar { + pb.set_style(ProgressStyle::with_template("Updating {pos}/{len}").unwrap()); + } if packages_to_update.is_empty() { error!("No updates available"); @@ -90,15 +95,23 @@ impl Updater { None, None, None, - multi_progress.clone(), + if quiet { + None + } else { + Some(multi_progress.clone()) + }, ) .await?; update_count += 1; - total_progress_bar.inc(1); + if let Some(ref pb) = total_progress_bar { + pb.inc(1); + } } - total_progress_bar.finish_and_clear(); - successln!( + if let Some(pb) = total_progress_bar { + pb.finish_and_clear(); + } + info!( "{} packages updated.", update_count.color(Color::BrightMagenta) ); diff --git a/src/registry/installed.rs b/src/registry/installed.rs index 9cdf7d3..ea50694 100644 --- a/src/registry/installed.rs +++ b/src/registry/installed.rs @@ -4,6 +4,7 @@ use anyhow::{Context, Result}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use tokio::fs; +use tracing::info; use crate::{ core::{ @@ -167,7 +168,7 @@ impl InstalledPackages { } resolved_packages.iter().for_each(|package| { - println!( + info!( "- [{}] {1}:{1}-{2} ({3}) ({4})", package.collection.clone().color(Color::BrightGreen), package.name.clone().color(Color::Blue), @@ -185,11 +186,10 @@ impl InstalledPackages { (count + 1, size + package.size), ); }); - println!(); - println!("{:<2} Installed:", ""); + info!("{:<2} Installed:", ""); for (collection, (count, size)) in total.iter() { - println!( + info!( "{:<4} {}: {} ({})", "", collection, @@ -204,7 +204,7 @@ impl InstalledPackages { (count_acc + count, value_acc + value) }); - println!( + info!( "{:<2} Total: {} ({})", "", count.color(Color::BrightYellow), diff --git a/src/registry/loader.rs b/src/registry/loader.rs index 7826475..13f5419 100644 --- a/src/registry/loader.rs +++ b/src/registry/loader.rs @@ -1,13 +1,8 @@ use anyhow::{Context, Result}; use tokio::fs; +use tracing::warn; -use crate::{ - core::{ - color::{Color, ColorExt}, - config::Repository, - }, - warnln, -}; +use crate::core::config::Repository; use super::fetcher::MetadataFetcher; @@ -27,7 +22,7 @@ impl MetadataLoader { .with_file_name(format!("{}.remote.bsum", repo.name)); let local_checksum = fs::read(&checksum_path).await.unwrap_or_default(); if checksum != local_checksum { - warnln!("Local registry is outdated. Refetching..."); + warn!("Local registry is outdated. Refetching..."); let content = fetcher.execute(repo).await; fs::write(checksum_path, &checksum).await?; return content; diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 609b1a0..4631d5e 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -9,6 +9,7 @@ use serde::Deserialize; use storage::{PackageStorage, RepositoryPackages}; use termion::cursor; use tokio::{fs, sync::Mutex}; +use tracing::{error, info}; use crate::{ core::{ @@ -16,11 +17,9 @@ use crate::{ config::CONFIG, util::{get_terminal_width, wrap_text}, }, - error, infoln, package::{ image::get_package_image_string, parse_package_query, update::Updater, ResolvedPackage, }, - successln, }; mod fetcher; @@ -91,6 +90,7 @@ impl PackageRegistry { portable_home: Option, portable_config: Option, yes: bool, + quiet: bool, ) -> Result<()> { self.storage .install_packages( @@ -101,6 +101,7 @@ impl PackageRegistry { portable_home, portable_config, yes, + quiet, ) .await } @@ -131,7 +132,7 @@ impl PackageRegistry { } else { "-" }; - println!( + info!( "[{}] [{}] {}: {} ({})", installed, pkg.collection.clone().color(Color::BrightGreen), @@ -142,7 +143,7 @@ impl PackageRegistry { }); if result.len() > limit { - println!( + info!( "\x1b[5mShowing {} of {} results\x1b[0m", limit, result.len() @@ -261,14 +262,14 @@ impl PackageRegistry { }); printable.extend(cursor::Down(1).to_string().as_bytes()); - println!("{}", String::from_utf8(printable).unwrap()); + info!("{}", String::from_utf8(printable).unwrap()); } Ok(()) } - pub async fn update(&self, package_names: Option<&[String]>) -> Result<()> { + pub async fn update(&self, package_names: Option<&[String]>, quiet: bool) -> Result<()> { let updater = Updater::new(package_names); - updater.execute(self).await + updater.execute(self, quiet).await } pub async fn info(&self, package_names: Option<&[String]>) -> Result<()> { @@ -292,7 +293,7 @@ impl PackageRegistry { } else { "-" }; - println!( + info!( "[{0}] [{1}] {2}:{3}-{4} ({5})", install_prefix.color(Color::Red), resolved_package.collection.color(Color::BrightGreen), @@ -313,14 +314,14 @@ impl PackageRegistry { self.storage.run(command, yes).await } - pub async fn use_package(&self, package_name: &str) -> Result<()> { + pub async fn use_package(&self, package_name: &str, quiet: bool) -> Result<()> { let installed_guard = self.installed_packages.lock().await; let resolved_package = self.storage.resolve_package(package_name, false)?; let result = installed_guard.use_package(&resolved_package).await; drop(installed_guard); match result { Ok(_) => { - successln!( + info!( "{} is linked to binary path", package_name.color(Color::Blue) ); @@ -339,6 +340,7 @@ impl PackageRegistry { None, None, false, + quiet, ) .await?; @@ -352,12 +354,12 @@ impl PackageRegistry { } pub fn select_single_package(packages: &[ResolvedPackage]) -> Result<&ResolvedPackage> { - infoln!( + info!( "Multiple packages available for {}", packages[0].package.pkg.clone().color(Color::Blue) ); for (i, package) in packages.iter().enumerate() { - println!( + info!( " [{}] [{}] {}: {}", i + 1, package.collection.clone().color(Color::BrightGreen), diff --git a/src/registry/storage.rs b/src/registry/storage.rs index ff64f50..d1f98be 100644 --- a/src/registry/storage.rs +++ b/src/registry/storage.rs @@ -16,6 +16,7 @@ use tokio::{ fs, sync::{Mutex, Semaphore}, }; +use tracing::{info, warn}; use crate::{ core::{ @@ -30,7 +31,6 @@ use crate::{ gen_package_info, parse_package_query, run::Runner, Package, PackageQuery, ResolvedPackage, }, registry::installed::InstalledPackages, - warnln, }; use super::select_single_package; @@ -81,6 +81,7 @@ impl PackageStorage { portable_home: Option, portable_config: Option, yes: bool, + quiet: bool, ) -> Result<()> { let resolved_packages: Result> = package_names .iter() @@ -126,7 +127,7 @@ impl PackageStorage { .into_iter() .filter_map(|(package, is_installed)| { if is_installed { - warnln!( + warn!( "{} is already installed - {}", package.package.full_name('/'), if force { "reinstalling" } else { "skipping" } @@ -145,11 +146,15 @@ impl PackageStorage { let installed_count = Arc::new(AtomicU64::new(0)); let multi_progress = Arc::new(MultiProgress::new()); - let total_progress_bar = - multi_progress.add(ProgressBar::new(resolved_packages.len() as u64)); + let total_progress_bar = if !quiet { + Some(multi_progress.add(ProgressBar::new(resolved_packages.len() as u64))) + } else { + None + }; - total_progress_bar - .set_style(ProgressStyle::with_template("Installing {pos}/{len}").unwrap()); + if let Some(pb) = &total_progress_bar { + pb.set_style(ProgressStyle::with_template("Installing {pos}/{len}").unwrap()); + } if CONFIG.parallel.unwrap_or_default() { let semaphore = Arc::new(Semaphore::new(CONFIG.parallel_limit.unwrap_or(2) as usize)); @@ -165,7 +170,11 @@ impl PackageStorage { let portable_home = portable_home.clone(); let portable_config = portable_config.clone(); let total_pb = total_progress_bar.clone(); - let multi_progress = multi_progress.clone(); + let multi_progress = if quiet { + None + } else { + Some(multi_progress.clone()) + }; let handle = tokio::spawn(async move { if let Err(e) = package @@ -183,7 +192,9 @@ impl PackageStorage { error!("{}", e); } else { ic.fetch_add(1, Ordering::Relaxed); - total_pb.inc(1); + if let Some(pb) = total_pb { + pb.inc(1); + } }; drop(permit); }); @@ -204,20 +215,28 @@ impl PackageStorage { portable.clone(), portable_home.clone(), portable_config.clone(), - multi_progress.clone(), + if quiet { + None + } else { + Some(multi_progress.clone()) + }, ) .await { error!("{}", e); } else { installed_count.fetch_add(1, Ordering::Relaxed); - total_progress_bar.inc(1); + if let Some(pb) = &total_progress_bar { + pb.inc(1); + } }; } } - total_progress_bar.finish_and_clear(); - println!( + if let Some(pb) = total_progress_bar { + pb.finish_and_clear(); + } + info!( "Installed {}/{} packages", installed_count.load(Ordering::Relaxed).color(Color::Blue), resolved_packages.len().color(Color::BrightBlue) @@ -456,7 +475,7 @@ impl PackageStorage { } } - println!( + info!( "Fetching {} from {} [{}]", inspect_type, url.color(Color::Blue), @@ -472,7 +491,7 @@ impl PackageStorage { } let output = String::from_utf8_lossy(&content).replace("\r", "\n"); - println!("\n{}", output); + info!("\n{}", output); Ok(()) }