pub mod audio; pub mod codec; pub mod dsp; pub mod network; pub mod core; pub mod app; pub mod config; pub mod identity; pub mod friends; pub mod presence; pub mod presence_net; pub mod theme; pub mod notify; pub mod screenshare; pub mod sanitize; pub mod avatar; pub mod recents; pub mod discovery; pub mod hotkeys; use std::path::PathBuf; use std::sync::OnceLock; /// Resolves the log file path once: `$XDG_STATE_HOME/peerspeak/peerspeak.log` /// (via `dirs::state_dir`), falling back to the system temp dir. Computed lazily /// so we never hardcode a per-user path. fn log_path() -> &'static PathBuf { static LOG_PATH: OnceLock = OnceLock::new(); LOG_PATH.get_or_init(|| { let mut dir = dirs::state_dir() .or_else(dirs::cache_dir) .unwrap_or_else(std::env::temp_dir); dir.push("peerspeak"); let _ = std::fs::create_dir_all(&dir); dir.push("peerspeak.log"); dir }) } /// The resolved log file path. Exposed so diagnostic tools (e.g. the audio /// probe) can tail the same log the app writes to. pub fn log_file_path() -> PathBuf { log_path().clone() } pub fn log_msg(msg: &str) { // Format the whole line into one buffer first, then emit it with a single // `write_all`. The file is opened with `O_APPEND`, so a lone `write()` is // atomic w.r.t. concurrent writers; building the line up front avoids the // multi-syscall `writeln!` path that would let threads interleave fragments. let line = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { Ok(time) => format!("[{}.{:03}] {}\n", time.as_secs(), time.subsec_millis(), msg), Err(_) => format!("{}\n", msg), }; if let Ok(mut file) = std::fs::OpenOptions::new() .create(true) .append(true) .open(log_path()) { use std::io::Write; let _ = file.write_all(line.as_bytes()); } }