use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; /// Relay/discovery posture, trading connectivity against how much the n0 /// infrastructure learns about you. See the network module for details. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum NetworkMode { /// n0 relay for NAT traversal, but no DNS presence beacon. Peer addresses /// come from the join ticket and gossip, so n0 only sees relayed-call /// metadata, never a standing "I'm online" record. Default. #[default] RelayNoDiscovery, /// Full n0 defaults: relay plus DNS publish/resolve (most convenient, /// most phone-home). N0Full, /// No relay, no discovery: direct hole-punching only. Fully serverless, /// but fails behind symmetric/CGNAT NATs with no fallback. DirectOnly, } impl NetworkMode { /// All variants, for presentation in a picker. pub const ALL: [NetworkMode; 3] = [NetworkMode::RelayNoDiscovery, NetworkMode::N0Full, NetworkMode::DirectOnly]; } impl std::fmt::Display for NetworkMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let label = match self { NetworkMode::RelayNoDiscovery => "Relay, no presence beacon", NetworkMode::N0Full => "n0 defaults (relay + DNS)", NetworkMode::DirectOnly => "Direct only (no relay)", }; f.write_str(label) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AppConfig { pub input_device: String, pub output_device: String, pub noise_gate_threshold: f32, #[serde(default)] pub network_mode: NetworkMode, } impl Default for AppConfig { fn default() -> Self { Self { input_device: "".to_string(), output_device: "".to_string(), noise_gate_threshold: 0.01, network_mode: NetworkMode::default(), } } } impl AppConfig { fn config_path() -> Option { dirs::config_dir().map(|mut p| { p.push("peerspeak"); p.push("config.json"); p }) } pub fn load() -> Self { if let Some(path) = Self::config_path() && let Ok(contents) = fs::read_to_string(&path) && let Ok(config) = serde_json::from_str(&contents) { return config; } Self::default() } pub fn save(&self) { if let Some(path) = Self::config_path() { if let Some(dir) = path.parent() { let _ = fs::create_dir_all(dir); } if let Ok(json) = serde_json::to_string_pretty(self) { let _ = fs::write(path, json); } } } }