use anyhow::{Context, Result};
use clap::ValueEnum;
use std::{
fs::{self, File, OpenOptions},
io::Write,
path::PathBuf,
};
use crate::{
cli::{Cli, Command},
command_handling,
os_specifics::OS,
panic_handling, term_output, utils,
};
pub const APP_NAME: &str = env!("CARGO_PKG_NAME");
pub const APP_INTERRUPTED_MSG: &str = concat!(
"\r\x1B[K",
env!("CARGO_PKG_NAME"),
" was interrupted by user..."
);
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum LogLevel {
Debug,
Info,
}
impl std::fmt::Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
LogLevel::Debug => write!(f, "DEBUG"),
LogLevel::Info => write!(f, "INFO"),
}
}
}
pub fn run(args: Cli, os: OS) -> Result<()> {
initialize_logging(args.logging)?;
panic_handling::initialize_panic_hook(args.no_color)?;
set_ctrl_c_handler()?;
let cmd_result = match args.command {
Command::Download(args) => command_handling::handle_download_cmd(args, os)?,
Command::Local(args) => command_handling::handle_local_cmd(args)?,
};
term_output::print_result(&cmd_result, args.no_color)?;
utils::save_hash_sum(&cmd_result, args.save)?;
Ok(())
}
pub fn initialize_logging(log_level: Option<LogLevel>) -> Result<()> {
create_data_dir()?;
if let Some(log_level) = log_level {
init_log_writer(log_level)?;
match log_level {
LogLevel::Debug => {
set_rust_backtrace();
log::debug!(
"{log_level} mode is enabled - {} version: {}",
APP_NAME,
env!("CARGO_PKG_VERSION")
);
log::debug!("Running on => {}", os_info::get());
}
LogLevel::Info => {
log::info!(
"{log_level} mode is enabled - {} version: {}",
APP_NAME,
env!("CARGO_PKG_VERSION")
);
}
}
}
Ok(())
}
pub fn set_ctrl_c_handler() -> Result<()> {
let exit_cmd = || {
log::info!("{APP_NAME} was interrupted by user...");
println!("{APP_INTERRUPTED_MSG}");
std::process::exit(1);
};
match ctrlc::set_handler(exit_cmd) {
Ok(_) => Ok(()),
Err(handler_err) => Err(anyhow::anyhow!(format!(
"Failed to set Ctrl-C signal handler - {:?}",
handler_err
))),
}
}
pub fn set_rust_backtrace() {
unsafe { std::env::set_var("RUST_BACKTRACE", "1") };
}
fn init_log_writer(log_level: LogLevel) -> Result<()> {
let mut log_file =
initialize_log_file().with_context(|| "Failed to create application log file")?;
match log_file.metadata() {
Ok(metadata) => {
if metadata.len() > 1 {
let _ = log_file.write(format!("\n{}\n", "-".repeat(100)).as_bytes());
}
}
Err(_) => {
let _ = log_file.write(format!("\n{}\n", "-".repeat(100)).as_bytes());
}
}
let config = simplelog::ConfigBuilder::new()
.set_time_format_rfc3339()
.build();
let log_level = match log_level {
LogLevel::Debug => simplelog::LevelFilter::Debug,
LogLevel::Info => simplelog::LevelFilter::Info,
};
simplelog::WriteLogger::init(log_level, config, log_file)?;
Ok(())
}
fn initialize_log_file() -> Result<File> {
const MAX_FILE_SIZE: u64 = 1000 * 1000; let path = log_file();
if fs::metadata(&path).is_ok_and(|metadata| metadata.len() > MAX_FILE_SIZE) {
let _ = fs::rename(&path, log_file_old()).or_else(|_| fs::remove_file(&path));
}
let log_file = OpenOptions::new().create(true).append(true).open(path)?;
Ok(log_file)
}
pub fn log_file() -> PathBuf {
data_dir().join(format!("{APP_NAME}.log"))
}
pub fn log_file_old() -> PathBuf {
data_dir().join(format!("{APP_NAME}.log.old"))
}
pub fn create_data_dir() -> Result<()> {
std::fs::create_dir_all(data_dir())
.with_context(|| "Failed to create application data directory")?;
Ok(())
}
pub fn data_dir() -> PathBuf {
match dirs::data_dir() {
Some(data_dir) => data_dir.join(APP_NAME),
None => PathBuf::new().join(".").join(APP_NAME),
}
}
pub fn version() -> String {
let author = env!("CARGO_PKG_AUTHORS");
let version = env!("CARGO_PKG_VERSION");
let repo = env!("CARGO_PKG_REPOSITORY");
let data_dir_path = utils::absolute_path_as_string(&data_dir());
format!(
"\
--- developed with ♥ in Rust
Authors : {author}
Version : {version}
Repository : {repo}
Data directory : {data_dir_path}
"
)
}