diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 2fb2446..35d5d5a 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -14,13 +14,15 @@ mod config; mod import; mod ui; -use crate::{Cache, Config, USER_AGENT}; +use crate::{Cache, Config, PKG_NAME, PKG_VERSION, USER_AGENT}; use clap::{Parser, Subcommand}; use log::LevelFilter; use simplelog::{ConfigBuilder as LogConfigBuilder, WriteLogger}; use std::borrow::Cow; -use std::fs::OpenOptions; -use std::path::PathBuf; +use std::ffi::OsString; +use std::fs::{self, File}; +use std::io; +use std::path::{Path, PathBuf}; use xdg::BaseDirectories; /// Command line Arguments. @@ -48,6 +50,44 @@ enum Commands { Analyze(analyze::Args), } +/// Append a numeric suffix (e.g., `.1`) to a path. +fn append_numeric_suffix_to_path(base_path: impl AsRef, number: usize) -> PathBuf { + let suffix: OsString = format!(".{number}").into(); + let new_extension = base_path.as_ref().extension().map_or_else( + || OsString::from(&suffix), + |ext| { + let mut extension = ext.to_os_string(); + extension.push(&suffix); + extension + }, + ); + base_path.as_ref().with_extension(new_extension) +} + +/// Rotate logfiles by renaming `` to `.0`, `.1` to `.2`, etc. +fn rotate_logfiles(base_path: impl AsRef) -> io::Result<()> { + let paths_to_rename = (0..7) + .rev() + .map(|i| { + ( + append_numeric_suffix_to_path(&base_path, i), + append_numeric_suffix_to_path(&base_path, i + 1), + ) + }) + .chain(std::iter::once(( + base_path.as_ref().to_path_buf(), + append_numeric_suffix_to_path(&base_path, 0), + ))); + for (old_path, new_path) in paths_to_rename { + fs::rename(old_path, new_path).or_else(|err| match err.kind() { + io::ErrorKind::NotFound => Ok(()), + _ => Err(err), + })?; + } + + Ok(()) +} + /// Main entry point. /// /// # Errors @@ -61,19 +101,22 @@ enum Commands { pub async fn main() -> crate::Result<()> { let args = Args::parse(); - let base_dirs = BaseDirectories::with_prefix(env!("CARGO_PKG_NAME"))?; + let base_dirs = BaseDirectories::with_prefix(PKG_NAME)?; // Initialize logging - let logfile_path = base_dirs.place_state_file("helicon.log")?; - let logfile = OpenOptions::new().append(true).open(logfile_path)?; + let logfile_path = base_dirs.place_state_file(format!("{PKG_NAME}.log"))?; + rotate_logfiles(&logfile_path)?; + let logfile = File::create(logfile_path)?; WriteLogger::init( LevelFilter::Debug, LogConfigBuilder::new() + .set_time_format_rfc3339() .add_filter_ignore_str("symphonia_core::probe") .build(), logfile, ) .expect("Failed to initialize logging"); + log::info!("Started {PKG_NAME} {PKG_VERSION}"); // Load configuration let config = base_dirs diff --git a/src/config.rs b/src/config.rs index 52cd646..ab4f31c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -414,6 +414,7 @@ mod builder { /// Add a file to be loaded to the configuration builder. Files added later will override /// values from previous files. pub fn with_file>(mut self, path: P) -> Self { + log::debug!("Reading config from file: {}", path.as_ref().display()); self.0 = self .0 .add_source(File::from(path.as_ref()).format(FileFormat::Toml)); @@ -423,6 +424,10 @@ mod builder { /// Add a file to be loaded to the configuration builder. Files added later will override /// values from previous files. pub fn with_str>(mut self, value: S) -> Self { + log::debug!( + "Reading config from string ({} bytes)", + value.as_ref().len() + ); self.0 = self .0 .add_source(File::from_str(value.as_ref(), FileFormat::Toml)); @@ -431,6 +436,7 @@ mod builder { /// Add the default configuration to the configuration builder. pub fn with_defaults(self) -> Self { + log::debug!("Reading default config as string"); self.with_str(DEFAULT_CONFIG) } @@ -472,3 +478,22 @@ impl Config { Self::load_from_str(DEFAULT_CONFIG) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_load_default() { + let config = Config::load_default().unwrap(); + let serialized = toml::to_string_pretty(&config).unwrap(); + println!("{serialized}"); + } + + #[test] + fn test_build_with_defaults() { + let config = Config::builder().with_defaults().build().unwrap(); + let serialized = toml::to_string_pretty(&config).unwrap(); + println!("{serialized}"); + } +} diff --git a/src/lib.rs b/src/lib.rs index 9da5664..f7072f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,5 +71,11 @@ pub use self::error::{ErrorType as Error, Result}; pub use self::taggedfile::TaggedFile; pub use self::taggedfilecollection::TaggedFileCollection; +/// Name of this package. +const PKG_NAME: &str = env!("CARGO_PKG_NAME"); + +/// Version of this package. +const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); + /// The User-Agent header that will be used for HTTP requests (i.e., for MusicBrainz). pub const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));