Files
p2p-chat/src/config.rs

260 lines
6.9 KiB
Rust

use anyhow::Result;
use directories::ProjectDirs;
use ratatui::style::Color;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AppConfig {
#[serde(default)]
pub network: NetworkConfig,
#[serde(default)]
pub media: MediaConfig,
#[serde(default)]
pub files: FileConfig,
#[serde(default)]
pub ui: UiConfig,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
network: NetworkConfig::default(),
media: MediaConfig::default(),
files: FileConfig::default(),
ui: UiConfig::default(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FileConfig {
#[serde(default = "default_timeout")]
pub default_timeout_seconds: u64,
}
fn default_timeout() -> u64 {
60
}
impl Default for FileConfig {
fn default() -> Self {
Self {
default_timeout_seconds: 60,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NetworkConfig {
pub topic: Option<String>,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self { topic: None }
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MediaConfig {
#[serde(default = "default_bitrate")]
pub mic_bitrate: u32,
#[serde(default)]
pub input_device: Option<String>,
#[serde(default)]
pub output_device: Option<String>,
#[serde(default = "default_volume")]
pub master_volume: f32,
#[serde(default = "default_true")]
pub noise_suppression: bool,
}
fn default_bitrate() -> u32 {
128000
}
fn default_volume() -> f32 {
1.0
}
fn default_true() -> bool {
true
}
impl Default for MediaConfig {
fn default() -> Self {
Self {
mic_bitrate: 128000,
input_device: None,
output_device: None,
master_volume: 1.0,
noise_suppression: true,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UiConfig {
pub text: String,
#[serde(default = "default_cyan")]
pub chat_border: String,
#[serde(default = "default_cyan")]
pub peer_border: String,
#[serde(default = "default_yellow")]
pub transfer_border: String,
pub self_name: String,
pub system_msg: String,
pub time: String,
#[serde(default)]
pub success: String,
#[serde(default)]
pub error: String,
#[serde(default)]
pub warning: String,
}
impl Default for UiConfig {
fn default() -> Self {
Self {
text: "white".to_string(),
self_name: "green".to_string(),
system_msg: "yellow".to_string(),
time: "dark_gray".to_string(),
chat_border: "cyan".to_string(),
peer_border: "cyan".to_string(),
transfer_border: "yellow".to_string(),
success: "green".to_string(),
error: "red".to_string(),
warning: "yellow".to_string(),
}
}
}
impl AppConfig {
pub fn load() -> Result<Self> {
let config_path = Self::get_config_path();
if !config_path.exists() {
// Create default config if it doesn't exist
let default_config = Self::default();
if let Some(parent) = config_path.parent() {
fs::create_dir_all(parent)?;
}
let toml = toml::to_string_pretty(&default_config)?;
fs::write(&config_path, toml)?;
return Ok(default_config);
}
let content = fs::read_to_string(&config_path)?;
let config: AppConfig = toml::from_str(&content)?;
Ok(config)
}
fn get_config_path() -> PathBuf {
if let Some(proj_dirs) = ProjectDirs::from("com", "p2p-chat", "p2p-chat") {
proj_dirs.config_dir().join("config.toml")
} else {
PathBuf::from("config.toml")
}
}
/// Save the current configuration to disk.
pub fn save(&self) -> Result<()> {
let config_path = Self::get_config_path();
if let Some(parent) = config_path.parent() {
fs::create_dir_all(parent)?;
}
let toml = toml::to_string_pretty(self)?;
fs::write(&config_path, toml)?;
Ok(())
}
}
// Helper to parse color strings
pub fn parse_color(color_str: &str) -> Color {
if color_str.starts_with('#') {
let hex = color_str.trim_start_matches('#');
if let Ok(val) = u32::from_str_radix(hex, 16) {
let (r, g, b) = if hex.len() == 3 {
// #RGB -> #RRGGBB
let r = ((val >> 8) & 0xF) * 17;
let g = ((val >> 4) & 0xF) * 17;
let b = (val & 0xF) * 17;
(r as u8, g as u8, b as u8)
} else {
// #RRGGBB
let r = (val >> 16) & 0xFF;
let g = (val >> 8) & 0xFF;
let b = val & 0xFF;
(r as u8, g as u8, b as u8)
};
return Color::Rgb(r, g, b);
}
}
match color_str.to_lowercase().as_str() {
"black" => Color::Black,
"red" => Color::Red,
"green" => Color::Green,
"yellow" => Color::Yellow,
"blue" => Color::Blue,
"magenta" => Color::Magenta,
"cyan" => Color::Cyan,
"gray" => Color::Gray,
"dark_gray" | "darkgray" => Color::DarkGray,
"light_red" | "lightred" => Color::LightRed,
"light_green" | "lightgreen" => Color::LightGreen,
"light_yellow" | "lightyellow" => Color::LightYellow,
"light_blue" | "lightblue" => Color::LightBlue,
"light_magenta" | "lightmagenta" => Color::LightMagenta,
"light_cyan" | "lightcyan" => Color::LightCyan,
"white" => Color::White,
_ => {
// Try hex parsing if needed, but for now fallback to White
Color::White
}
}
}
// Runtime Theme struct derived from config
#[derive(Debug, Clone)]
pub struct Theme {
pub chat_border: Color,
pub peer_border: Color,
pub transfer_border: Color,
pub text: Color,
pub self_name: Color,
pub system_msg: Color,
pub time: Color,
pub success: Color,
pub error: Color,
pub warning: Color,
}
impl From<UiConfig> for Theme {
fn from(cfg: UiConfig) -> Self {
Self {
chat_border: parse_color(&cfg.chat_border),
peer_border: parse_color(&cfg.peer_border),
transfer_border: parse_color(&cfg.transfer_border),
text: parse_color(&cfg.text),
self_name: parse_color(&cfg.self_name),
system_msg: parse_color(&cfg.system_msg),
time: parse_color(&cfg.time),
success: parse_color(&cfg.success),
error: parse_color(&cfg.error),
warning: parse_color(&cfg.warning),
}
}
}
fn default_yellow() -> String {
"yellow".to_string()
}
fn default_cyan() -> String {
"cyan".to_string()
}