mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
refactor: introduce cargo workspace + psysonic-core crate (M1/7)
First milestone of the workspace crate split. Sets up the workspace
skeleton and extracts shared logging + cross-crate port traits into a
new psysonic-core crate.
src-tauri/Cargo.toml now defines [workspace]; top package
becomes the workspace root.
crates/psysonic-core/ new lib crate, no Tauri-handler code:
src/logging.rs full logging facade (was src/logging.rs)
src/ports.rs PlaybackQuery + AnalysisOrchestrator
trait declarations (no impls yet)
The top crate re-exports `psysonic_core::logging` and the
`app_eprintln!` / `app_deprintln!` macros so every existing
`crate::logging::*` and `crate::app_eprintln!` callsite keeps working
unchanged.
Port traits intentionally take `tauri::AppHandle` — psysonic-core is a
workspace-internal crate, so depending on Tauri here is a feature, not
a leak. Implementers will register themselves as
`Arc<dyn PlaybackQuery>` / `Arc<dyn AnalysisOrchestrator>` Tauri State
in M2/M3.
Behaviour preserving. Cargo check + clippy --workspace clean.
This commit is contained in:
@@ -6,8 +6,10 @@ mod analysis_cache;
|
||||
mod analysis_runtime;
|
||||
pub mod cli;
|
||||
mod discord;
|
||||
pub(crate) mod logging;
|
||||
mod lib_commands;
|
||||
|
||||
pub use psysonic_core::logging;
|
||||
pub use psysonic_core::{app_eprintln, app_deprintln};
|
||||
#[cfg(target_os = "windows")]
|
||||
mod taskbar_win;
|
||||
mod tray_runtime;
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
#[cfg(unix)]
|
||||
use libc;
|
||||
use std::collections::VecDeque;
|
||||
use std::io::Write;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum LoggingMode {
|
||||
Off = 0,
|
||||
Normal = 1,
|
||||
Debug = 2,
|
||||
}
|
||||
|
||||
static LOGGING_MODE: AtomicU8 = AtomicU8::new(LoggingMode::Normal as u8);
|
||||
const LOG_BUFFER_MAX_LINES: usize = 20_000;
|
||||
|
||||
fn log_buffer() -> &'static Mutex<VecDeque<String>> {
|
||||
static LOG_BUFFER: OnceLock<Mutex<VecDeque<String>>> = OnceLock::new();
|
||||
LOG_BUFFER.get_or_init(|| Mutex::new(VecDeque::with_capacity(LOG_BUFFER_MAX_LINES)))
|
||||
}
|
||||
|
||||
/// Shared runtime file used by CLI `--tail` to read normal/debug log channel.
|
||||
pub fn cli_log_channel_path() -> std::path::PathBuf {
|
||||
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
||||
if !dir.is_empty() {
|
||||
return std::path::PathBuf::from(dir).join("psysonic-cli.log");
|
||||
}
|
||||
}
|
||||
std::env::temp_dir().join("psysonic-cli.log")
|
||||
}
|
||||
|
||||
fn parse_logging_mode(mode: &str) -> Option<LoggingMode> {
|
||||
match mode.trim().to_ascii_lowercase().as_str() {
|
||||
"off" => Some(LoggingMode::Off),
|
||||
"normal" => Some(LoggingMode::Normal),
|
||||
"debug" => Some(LoggingMode::Debug),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_logging_mode_from_str(mode: &str) -> Result<(), String> {
|
||||
let parsed = parse_logging_mode(mode)
|
||||
.ok_or_else(|| "invalid logging mode (expected: off | normal | debug)".to_string())?;
|
||||
LOGGING_MODE.store(parsed as u8, Ordering::Release);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn current_mode() -> LoggingMode {
|
||||
match LOGGING_MODE.load(Ordering::Acquire) {
|
||||
0 => LoggingMode::Off,
|
||||
2 => LoggingMode::Debug,
|
||||
_ => LoggingMode::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_log_normal() -> bool {
|
||||
!matches!(current_mode(), LoggingMode::Off)
|
||||
}
|
||||
|
||||
pub fn should_log_debug() -> bool {
|
||||
matches!(current_mode(), LoggingMode::Debug)
|
||||
}
|
||||
|
||||
pub fn append_log_line(line: String) {
|
||||
let mut buf = log_buffer().lock().unwrap();
|
||||
if buf.len() >= LOG_BUFFER_MAX_LINES {
|
||||
buf.pop_front();
|
||||
}
|
||||
buf.push_back(line.clone());
|
||||
drop(buf);
|
||||
let path = cli_log_channel_path();
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
|
||||
let _ = writeln!(f, "{}", line);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export_logs_to_file(path: &str) -> Result<usize, String> {
|
||||
let snapshot = {
|
||||
let buf = log_buffer().lock().unwrap();
|
||||
if buf.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
let mut s = buf.iter().cloned().collect::<Vec<_>>().join("\n");
|
||||
s.push('\n');
|
||||
s
|
||||
}
|
||||
};
|
||||
std::fs::write(path, snapshot).map_err(|e| e.to_string())?;
|
||||
let lines = {
|
||||
let buf = log_buffer().lock().unwrap();
|
||||
buf.len()
|
||||
};
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
pub(crate) fn log_timestamp_local() -> String {
|
||||
let now = ::std::time::SystemTime::now()
|
||||
.duration_since(::std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default();
|
||||
let millis = now.subsec_millis();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::ffi::CStr;
|
||||
let secs: libc::time_t = now.as_secs() as libc::time_t;
|
||||
let mut tm: libc::tm = unsafe { std::mem::zeroed() };
|
||||
let mut date_buf: [libc::c_char; 64] = [0; 64];
|
||||
let mut tz_buf: [libc::c_char; 16] = [0; 16];
|
||||
let date_fmt = b"%Y-%m-%d %H:%M:%S\0";
|
||||
let tz_fmt = b"%z\0";
|
||||
|
||||
unsafe {
|
||||
if libc::localtime_r(&secs as *const libc::time_t, &mut tm as *mut libc::tm).is_null() {
|
||||
return format!("{}.{:03}", now.as_secs(), millis);
|
||||
}
|
||||
let date_ok = libc::strftime(
|
||||
date_buf.as_mut_ptr(),
|
||||
date_buf.len(),
|
||||
date_fmt.as_ptr().cast(),
|
||||
&tm as *const libc::tm,
|
||||
);
|
||||
if date_ok == 0 {
|
||||
return format!("{}.{:03}", now.as_secs(), millis);
|
||||
}
|
||||
let tz_ok = libc::strftime(
|
||||
tz_buf.as_mut_ptr(),
|
||||
tz_buf.len(),
|
||||
tz_fmt.as_ptr().cast(),
|
||||
&tm as *const libc::tm,
|
||||
);
|
||||
|
||||
let date = CStr::from_ptr(date_buf.as_ptr()).to_string_lossy();
|
||||
if tz_ok == 0 {
|
||||
return format!("{}.{:03}", date, millis);
|
||||
}
|
||||
let tz = CStr::from_ptr(tz_buf.as_ptr()).to_string_lossy();
|
||||
return format!("{}.{:03} {}", date, millis, tz);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
format!("{}.{:03}", now.as_secs(), millis)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! app_eprintln {
|
||||
() => {{
|
||||
if $crate::logging::should_log_normal() {
|
||||
let ts = $crate::logging::log_timestamp_local();
|
||||
let line = format!("[{}]", ts);
|
||||
$crate::logging::append_log_line(line.clone());
|
||||
::std::eprintln!("{}", line);
|
||||
}
|
||||
}};
|
||||
($($arg:tt)*) => {{
|
||||
if $crate::logging::should_log_normal() {
|
||||
let ts = $crate::logging::log_timestamp_local();
|
||||
let line = format!("[{}] {}", ts, format_args!($($arg)*));
|
||||
$crate::logging::append_log_line(line.clone());
|
||||
::std::eprintln!("{}", line);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! app_deprintln {
|
||||
() => {{
|
||||
if $crate::logging::should_log_debug() {
|
||||
let ts = $crate::logging::log_timestamp_local();
|
||||
let line = format!("[{}]", ts);
|
||||
$crate::logging::append_log_line(line.clone());
|
||||
::std::eprintln!("{}", line);
|
||||
}
|
||||
}};
|
||||
($($arg:tt)*) => {{
|
||||
if $crate::logging::should_log_debug() {
|
||||
let ts = $crate::logging::log_timestamp_local();
|
||||
let line = format!("[{}] {}", ts, format_args!($($arg)*));
|
||||
$crate::logging::append_log_line(line.clone());
|
||||
::std::eprintln!("{}", line);
|
||||
}
|
||||
}};
|
||||
}
|
||||
Reference in New Issue
Block a user