use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use chrono::Local;
use log::error;
use crate::config::get_log_path;
use crate::integrations::ZahirResult;
pub fn fatal_error_handler<T, E: std::fmt::Display>(result: Result<T, E>, msg: &str) -> T {
match result {
Ok(v) => v,
Err(e) => {
let line = msg.replacen("{}", &e.to_string(), 1);
error!("{line}");
try_append_fatal_line(&line);
exit_error();
}
}
}
static UBLX_LOG_FILE_PATH: OnceLock<PathBuf> = OnceLock::new();
pub fn set_index_dir_for_ublx_log(dir_to_ublx: &Path) {
let _ = UBLX_LOG_FILE_PATH.set(get_log_path(dir_to_ublx));
}
pub fn install_panic_hook_with_ublx_log(dir_to_ublx: &Path) {
set_index_dir_for_ublx_log(dir_to_ublx);
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
try_write_panic_to_log(info);
previous(info);
}));
}
pub fn try_append_fatal_line(message_ref: &str) {
let Some(log_path) = UBLX_LOG_FILE_PATH.get() else {
return;
};
let ts = Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
let _ = append_line_to_log_path(log_path, &format!("[{ts}] fatal: {message_ref}"));
}
pub fn try_write_panic_to_log(info: &std::panic::PanicHookInfo) {
let Some(log_path) = UBLX_LOG_FILE_PATH.get() else {
return;
};
let payload = if let Some(s) = info.payload().downcast_ref::<&str>() {
*s
} else if let Some(s) = info.payload().downcast_ref::<String>() {
s.as_str()
} else {
"(panic payload not a string)"
};
let loc = info.location().map_or_else(
|| "unknown location".to_string(),
|l| format!("{}:{}:{}", l.file(), l.line(), l.column()),
);
let ts = Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
let _ = append_line_to_log_path(log_path, &format!("[{ts}] panic at {loc}: {payload}"));
}
fn append_line_to_log_path(log_path: &Path, line: &str) -> std::io::Result<()> {
let mut f = File::options().create(true).append(true).open(log_path)?;
writeln!(f, "{line}")?;
Ok(())
}
pub struct NefaxZahirErrors;
impl NefaxZahirErrors {
pub const LOG_FILE_BASENAME: &'static str = "ublx.log";
pub const ZAHIRSCAN_FAILURES_HEADER: &'static str = "Zahirscan failures:";
pub const NEFAXER_LOG_LINE_PREFIX: &'static str = "Nefaxer failure: ";
#[must_use]
pub fn nefax_failed(err: impl std::fmt::Display) -> String {
format!("nefax failed: {err}")
}
#[must_use]
pub fn zahir_sequential_failed(err: impl std::fmt::Display) -> String {
format!("zahir (sequential) failed: {err}")
}
#[must_use]
pub fn zahir_stream_failed(err: impl std::fmt::Display) -> String {
format!("zahir (stream) failed: {err}")
}
#[must_use]
pub fn zahir_failures_log_write_failed(err: impl std::fmt::Display) -> String {
format!(
"failed to write zahir failures to {}: {err}",
Self::LOG_FILE_BASENAME
)
}
pub const ZAHIR_THREAD_PANICKED: &'static str = "zahir thread panicked";
}
pub const EXIT_ERROR: i32 = 1;
pub const EXIT_CLI_USAGE: i32 = 2;
pub fn exit_error() -> ! {
std::process::exit(EXIT_ERROR)
}
pub fn exit_cli_usage() -> ! {
std::process::exit(EXIT_CLI_USAGE)
}
pub fn exit_if_enhance_all_without_headless(enhance_all: bool, snapshot_headless: bool) {
if enhance_all && !snapshot_headless {
eprintln!("ublx: --enhance-all requires --snapshot-only or --full-snapshot");
exit_cli_usage();
}
}
pub trait ZahirResultExt {
fn iter_failures(&self) -> impl Iterator<Item = (&String, &String)>;
fn failures(&self) -> Vec<(&String, &String)> {
self.iter_failures().collect()
}
}
impl ZahirResultExt for ZahirResult {
fn iter_failures(&self) -> impl Iterator<Item = (&String, &String)> {
self.phase1_failed
.iter()
.chain(self.phase2_failed.iter())
.map(|(p, e)| (p, e))
}
}
fn append_failures_zahirscan(
f: &mut File,
header: &str,
failures: &[(&String, &String)],
) -> std::io::Result<()> {
writeln!(f, "{header}")?;
for (p, e) in failures {
writeln!(f, " {p}: {e}")?;
}
Ok(())
}
pub fn write_zahir_failures_to_log(
dir_to_ublx: &Path,
zahir_result: &ZahirResult,
) -> std::io::Result<()> {
let header = NefaxZahirErrors::ZAHIRSCAN_FAILURES_HEADER;
let failures = zahir_result.failures();
if failures.is_empty() {
return Ok(());
}
let mut f = create_log_file(dir_to_ublx)?;
append_failures_zahirscan(&mut f, header, &failures)?;
Ok(())
}
pub fn write_zahir_failures_to_log_error(dir_to_ublx: &Path, zahir_result: &ZahirResult) {
if let Err(e) = write_zahir_failures_to_log(dir_to_ublx, zahir_result) {
error!("{}", NefaxZahirErrors::zahir_failures_log_write_failed(&e));
}
}
pub fn write_nefax_error_to_log(
dir_to_ublx: &Path,
err: &impl std::fmt::Display,
) -> std::io::Result<()> {
let mut f = create_log_file(dir_to_ublx)?;
writeln!(f, "{}{err}", NefaxZahirErrors::NEFAXER_LOG_LINE_PREFIX)?;
Ok(())
}
fn create_log_file(dir_to_ublx: &Path) -> std::io::Result<File> {
let log_path = get_log_path(dir_to_ublx);
File::options().create(true).append(true).open(log_path)
}