diff --git a/Cargo.lock b/Cargo.lock index 1bc3b76..0448f13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2223,13 +2223,11 @@ dependencies = [ "bytes", "clap", "dirs", - "libloading", "serde", "serde_json", "tokio", "toml", "tracing", - "tracing-subscriber", "trx-app", "trx-core", "trx-frontend", @@ -2253,6 +2251,7 @@ dependencies = [ name = "trx-frontend" version = "0.1.0" dependencies = [ + "bytes", "tokio", "trx-core", ] @@ -2272,6 +2271,7 @@ dependencies = [ "tracing", "trx-core", "trx-frontend", + "trx-protocol", ] [[package]] @@ -2294,6 +2294,7 @@ dependencies = [ "tracing", "trx-core", "trx-frontend", + "trx-protocol", ] [[package]] @@ -2321,7 +2322,6 @@ dependencies = [ "clap", "cpal", "dirs", - "libloading", "opus", "serde", "serde_json", @@ -2329,7 +2329,6 @@ dependencies = [ "tokio-serial", "toml", "tracing", - "tracing-subscriber", "trx-app", "trx-backend", "trx-core", diff --git a/src/trx-client/Cargo.toml b/src/trx-client/Cargo.toml index 6726b12..e5da131 100644 --- a/src/trx-client/Cargo.toml +++ b/src/trx-client/Cargo.toml @@ -13,10 +13,8 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } toml = { workspace = true } tracing = { workspace = true } -tracing-subscriber = { workspace = true } clap = { workspace = true, features = ["derive"] } dirs = "6" -libloading = "0.8" bytes = "1" trx-app = { path = "../trx-app" } trx-core = { path = "../trx-core" } diff --git a/src/trx-client/src/config.rs b/src/trx-client/src/config.rs index d3071a2..64670fd 100644 --- a/src/trx-client/src/config.rs +++ b/src/trx-client/src/config.rs @@ -14,6 +14,7 @@ use std::net::IpAddr; use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; +use trx_app::{ConfigError, ConfigFile}; /// Top-level client configuration structure. #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -189,44 +190,13 @@ pub struct HttpJsonAuthConfig { impl ClientConfig { /// Load configuration from a specific file path. pub fn load_from_file(path: &Path) -> Result { - let contents = std::fs::read_to_string(path) - .map_err(|e| ConfigError::ReadError(path.to_path_buf(), e.to_string()))?; - - toml::from_str(&contents) - .map_err(|e| ConfigError::ParseError(path.to_path_buf(), e.to_string())) + ::load_from_file(path) } /// Load configuration from the default search paths. /// Returns default config if no config file is found. pub fn load_from_default_paths() -> Result<(Self, Option), ConfigError> { - let search_paths = Self::default_search_paths(); - - for path in search_paths { - if path.exists() { - let config = Self::load_from_file(&path)?; - return Ok((config, Some(path))); - } - } - - Ok((Self::default(), None)) - } - - /// Get the default search paths for config files. - pub fn default_search_paths() -> Vec { - let mut paths = Vec::new(); - - // Current directory - paths.push(PathBuf::from("trx-client.toml")); - - // XDG config directory - if let Some(config_dir) = dirs::config_dir() { - paths.push(config_dir.join("trx-rs").join("client.toml")); - } - - // System-wide config - paths.push(PathBuf::from("/etc/trx-rs/client.toml")); - - paths + ::load_from_default_paths() } /// Generate an example configuration as a TOML string. @@ -263,40 +233,22 @@ impl ClientConfig { } } -/// Errors that can occur when loading configuration. -#[derive(Debug)] -pub enum ConfigError { - /// Failed to read the config file - ReadError(PathBuf, String), - /// Failed to parse the config file - ParseError(PathBuf, String), -} +impl ConfigFile for ClientConfig { + fn config_filename() -> &'static str { + "client.toml" + } -impl std::fmt::Display for ConfigError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::ReadError(path, err) => { - write!( - f, - "failed to read config file '{}': {}", - path.display(), - err - ) - } - Self::ParseError(path, err) => { - write!( - f, - "failed to parse config file '{}': {}", - path.display(), - err - ) - } + fn default_search_paths() -> Vec { + let mut paths = Vec::new(); + paths.push(PathBuf::from("trx-client.toml")); + if let Some(config_dir) = dirs::config_dir() { + paths.push(config_dir.join("trx-rs").join("client.toml")); } + paths.push(PathBuf::from("/etc/trx-rs/client.toml")); + paths } } -impl std::error::Error for ConfigError {} - #[cfg(test)] mod tests { use super::*; diff --git a/src/trx-client/src/plugins.rs b/src/trx-client/src/plugins.rs deleted file mode 100644 index 4456f17..0000000 --- a/src/trx-client/src/plugins.rs +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Stanislaw Grams -// -// SPDX-License-Identifier: BSD-2-Clause - -use std::ffi::OsStr; -use std::path::{Path, PathBuf}; - -use libloading::{Library, Symbol}; -use tracing::{info, warn}; - -const PLUGIN_ENV: &str = "TRX_PLUGIN_DIRS"; -const PLUGIN_ENTRYPOINT: &str = "trx_register"; - -#[cfg(windows)] -const PATH_SEPARATOR: char = ';'; -#[cfg(not(windows))] -const PATH_SEPARATOR: char = ':'; - -#[cfg(windows)] -const PLUGIN_EXTENSIONS: &[&str] = &["dll"]; -#[cfg(target_os = "macos")] -const PLUGIN_EXTENSIONS: &[&str] = &["dylib"]; -#[cfg(all(unix, not(target_os = "macos")))] -const PLUGIN_EXTENSIONS: &[&str] = &["so"]; - -pub fn load_plugins() -> Vec { - let mut libraries = Vec::new(); - let search_paths = plugin_search_paths(); - - if search_paths.is_empty() { - return libraries; - } - - info!("Plugin search paths: {:?}", search_paths); - - for path in search_paths { - if let Err(err) = load_plugins_from_dir(&path, &mut libraries) { - warn!("Plugin scan failed for {:?}: {}", path, err); - } - } - - libraries -} - -fn load_plugins_from_dir(path: &Path, libraries: &mut Vec) -> std::io::Result<()> { - if !path.exists() { - return Ok(()); - } - - for entry in std::fs::read_dir(path)? { - let entry = entry?; - let path = entry.path(); - if !path.is_file() { - continue; - } - if !is_plugin_file(&path) { - continue; - } - - unsafe { - match Library::new(&path) { - Ok(lib) => { - if let Err(err) = register_library(&lib, &path) { - warn!("Plugin {:?} failed to register: {}", path, err); - continue; - } - info!("Loaded plugin {:?}", path); - libraries.push(lib); - } - Err(err) => { - warn!("Failed to load plugin {:?}: {}", path, err); - } - } - } - } - - Ok(()) -} - -unsafe fn register_library(lib: &Library, path: &Path) -> Result<(), String> { - let entry: Symbol = lib - .get(PLUGIN_ENTRYPOINT.as_bytes()) - .map_err(|e| format!("missing entrypoint {}: {}", PLUGIN_ENTRYPOINT, e))?; - entry(); - info!("Registered plugin {:?}", path); - Ok(()) -} - -fn plugin_search_paths() -> Vec { - let mut paths = Vec::new(); - - if let Ok(env_paths) = std::env::var(PLUGIN_ENV) { - for raw in env_paths.split(PATH_SEPARATOR) { - if raw.trim().is_empty() { - continue; - } - paths.push(PathBuf::from(raw)); - } - } - - paths.push(PathBuf::from("plugins")); - - if let Some(config_dir) = dirs::config_dir() { - paths.push(config_dir.join("trx-rs").join("plugins")); - } - - paths -} - -fn is_plugin_file(path: &Path) -> bool { - path.extension() - .and_then(OsStr::to_str) - .map(|ext| PLUGIN_EXTENSIONS.iter().any(|e| ext.eq_ignore_ascii_case(e))) - .unwrap_or(false) -} diff --git a/src/trx-server/Cargo.toml b/src/trx-server/Cargo.toml index 4c65180..a1ade6e 100644 --- a/src/trx-server/Cargo.toml +++ b/src/trx-server/Cargo.toml @@ -14,10 +14,8 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } toml = { workspace = true } tracing = { workspace = true } -tracing-subscriber = { workspace = true } clap = { workspace = true, features = ["derive"] } dirs = "6" -libloading = "0.8" bytes = "1" cpal = "0.15" opus = "0.3" diff --git a/src/trx-server/src/config.rs b/src/trx-server/src/config.rs index 81918f3..850abb8 100644 --- a/src/trx-server/src/config.rs +++ b/src/trx-server/src/config.rs @@ -14,6 +14,7 @@ use std::net::IpAddr; use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; +use trx_app::{ConfigError, ConfigFile}; use trx_core::rig::state::RigMode; @@ -204,44 +205,13 @@ impl Default for AudioConfig { impl ServerConfig { /// Load configuration from a specific file path. pub fn load_from_file(path: &Path) -> Result { - let contents = std::fs::read_to_string(path) - .map_err(|e| ConfigError::ReadError(path.to_path_buf(), e.to_string()))?; - - toml::from_str(&contents) - .map_err(|e| ConfigError::ParseError(path.to_path_buf(), e.to_string())) + ::load_from_file(path) } /// Load configuration from the default search paths. /// Returns default config if no config file is found. pub fn load_from_default_paths() -> Result<(Self, Option), ConfigError> { - let search_paths = Self::default_search_paths(); - - for path in search_paths { - if path.exists() { - let config = Self::load_from_file(&path)?; - return Ok((config, Some(path))); - } - } - - Ok((Self::default(), None)) - } - - /// Get the default search paths for config files. - pub fn default_search_paths() -> Vec { - let mut paths = Vec::new(); - - // Current directory - paths.push(PathBuf::from("trx-server.toml")); - - // XDG config directory - if let Some(config_dir) = dirs::config_dir() { - paths.push(config_dir.join("trx-rs").join("server.toml")); - } - - // System-wide config - paths.push(PathBuf::from("/etc/trx-rs/server.toml")); - - paths + ::load_from_default_paths() } /// Generate an example configuration as a TOML string. @@ -274,40 +244,22 @@ impl ServerConfig { } } -/// Errors that can occur when loading configuration. -#[derive(Debug)] -pub enum ConfigError { - /// Failed to read the config file - ReadError(PathBuf, String), - /// Failed to parse the config file - ParseError(PathBuf, String), -} +impl ConfigFile for ServerConfig { + fn config_filename() -> &'static str { + "server.toml" + } -impl std::fmt::Display for ConfigError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::ReadError(path, err) => { - write!( - f, - "failed to read config file '{}': {}", - path.display(), - err - ) - } - Self::ParseError(path, err) => { - write!( - f, - "failed to parse config file '{}': {}", - path.display(), - err - ) - } + fn default_search_paths() -> Vec { + let mut paths = Vec::new(); + paths.push(PathBuf::from("trx-server.toml")); + if let Some(config_dir) = dirs::config_dir() { + paths.push(config_dir.join("trx-rs").join("server.toml")); } + paths.push(PathBuf::from("/etc/trx-rs/server.toml")); + paths } } -impl std::error::Error for ConfigError {} - #[cfg(test)] mod tests { use super::*; diff --git a/src/trx-server/src/plugins.rs b/src/trx-server/src/plugins.rs deleted file mode 100644 index 4456f17..0000000 --- a/src/trx-server/src/plugins.rs +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Stanislaw Grams -// -// SPDX-License-Identifier: BSD-2-Clause - -use std::ffi::OsStr; -use std::path::{Path, PathBuf}; - -use libloading::{Library, Symbol}; -use tracing::{info, warn}; - -const PLUGIN_ENV: &str = "TRX_PLUGIN_DIRS"; -const PLUGIN_ENTRYPOINT: &str = "trx_register"; - -#[cfg(windows)] -const PATH_SEPARATOR: char = ';'; -#[cfg(not(windows))] -const PATH_SEPARATOR: char = ':'; - -#[cfg(windows)] -const PLUGIN_EXTENSIONS: &[&str] = &["dll"]; -#[cfg(target_os = "macos")] -const PLUGIN_EXTENSIONS: &[&str] = &["dylib"]; -#[cfg(all(unix, not(target_os = "macos")))] -const PLUGIN_EXTENSIONS: &[&str] = &["so"]; - -pub fn load_plugins() -> Vec { - let mut libraries = Vec::new(); - let search_paths = plugin_search_paths(); - - if search_paths.is_empty() { - return libraries; - } - - info!("Plugin search paths: {:?}", search_paths); - - for path in search_paths { - if let Err(err) = load_plugins_from_dir(&path, &mut libraries) { - warn!("Plugin scan failed for {:?}: {}", path, err); - } - } - - libraries -} - -fn load_plugins_from_dir(path: &Path, libraries: &mut Vec) -> std::io::Result<()> { - if !path.exists() { - return Ok(()); - } - - for entry in std::fs::read_dir(path)? { - let entry = entry?; - let path = entry.path(); - if !path.is_file() { - continue; - } - if !is_plugin_file(&path) { - continue; - } - - unsafe { - match Library::new(&path) { - Ok(lib) => { - if let Err(err) = register_library(&lib, &path) { - warn!("Plugin {:?} failed to register: {}", path, err); - continue; - } - info!("Loaded plugin {:?}", path); - libraries.push(lib); - } - Err(err) => { - warn!("Failed to load plugin {:?}: {}", path, err); - } - } - } - } - - Ok(()) -} - -unsafe fn register_library(lib: &Library, path: &Path) -> Result<(), String> { - let entry: Symbol = lib - .get(PLUGIN_ENTRYPOINT.as_bytes()) - .map_err(|e| format!("missing entrypoint {}: {}", PLUGIN_ENTRYPOINT, e))?; - entry(); - info!("Registered plugin {:?}", path); - Ok(()) -} - -fn plugin_search_paths() -> Vec { - let mut paths = Vec::new(); - - if let Ok(env_paths) = std::env::var(PLUGIN_ENV) { - for raw in env_paths.split(PATH_SEPARATOR) { - if raw.trim().is_empty() { - continue; - } - paths.push(PathBuf::from(raw)); - } - } - - paths.push(PathBuf::from("plugins")); - - if let Some(config_dir) = dirs::config_dir() { - paths.push(config_dir.join("trx-rs").join("plugins")); - } - - paths -} - -fn is_plugin_file(path: &Path) -> bool { - path.extension() - .and_then(OsStr::to_str) - .map(|ext| PLUGIN_EXTENSIONS.iter().any(|e| ext.eq_ignore_ascii_case(e))) - .unwrap_or(false) -}