use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Prefs {
#[serde(skip_serializing_if = "Option::is_none")]
pub theme: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_server: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub streams: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub show_help_on_start: Option<bool>,
}
impl Prefs {
pub fn path() -> Option<PathBuf> {
dirs::config_dir().map(|p| p.join("xfr").join("prefs.toml"))
}
pub fn load() -> Self {
Self::path()
.and_then(|p| fs::read_to_string(p).ok())
.and_then(|s| toml::from_str(&s).ok())
.unwrap_or_default()
}
pub fn save(&self) -> anyhow::Result<()> {
if let Some(path) = Self::path() {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, toml::to_string_pretty(self)?)?;
}
Ok(())
}
pub fn with_overrides(mut self, cli_theme: Option<&str>, config_theme: Option<&str>) -> Self {
if let Some(t) = cli_theme.filter(|t| *t != "default") {
self.theme = Some(t.to_string());
} else if let Some(t) = config_theme {
self.theme = Some(t.to_string());
}
self
}
pub fn theme_name(&self) -> &str {
self.theme.as_deref().unwrap_or("default")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prefs_default() {
let prefs = Prefs::default();
assert!(prefs.theme.is_none());
assert!(prefs.last_server.is_none());
assert!(prefs.streams.is_none());
}
#[test]
fn test_prefs_serialization() {
let prefs = Prefs {
theme: Some("dracula".to_string()),
last_server: Some("192.168.1.1".to_string()),
streams: Some(4),
show_help_on_start: Some(false),
};
let toml_str = toml::to_string_pretty(&prefs).unwrap();
assert!(toml_str.contains("theme = \"dracula\""));
assert!(toml_str.contains("last_server"));
assert!(toml_str.contains("streams = 4"));
let loaded: Prefs = toml::from_str(&toml_str).unwrap();
assert_eq!(loaded.theme, Some("dracula".to_string()));
assert_eq!(loaded.streams, Some(4));
}
#[test]
fn test_prefs_skip_none_fields() {
let prefs = Prefs {
theme: Some("default".to_string()),
last_server: None,
streams: None,
show_help_on_start: None,
};
let toml_str = toml::to_string_pretty(&prefs).unwrap();
assert!(toml_str.contains("theme"));
assert!(!toml_str.contains("last_server"));
assert!(!toml_str.contains("streams"));
}
#[test]
fn test_with_overrides_cli_wins() {
let prefs = Prefs {
theme: Some("saved".to_string()),
..Default::default()
};
let result = prefs.with_overrides(Some("cli"), Some("config"));
assert_eq!(result.theme, Some("cli".to_string()));
}
#[test]
fn test_with_overrides_config_wins_over_saved() {
let prefs = Prefs {
theme: Some("saved".to_string()),
..Default::default()
};
let result = prefs.with_overrides(Some("default"), Some("config"));
assert_eq!(result.theme, Some("config".to_string()));
}
#[test]
fn test_with_overrides_keeps_saved() {
let prefs = Prefs {
theme: Some("saved".to_string()),
..Default::default()
};
let result = prefs.with_overrides(Some("default"), None);
assert_eq!(result.theme, Some("saved".to_string()));
}
#[test]
fn test_theme_name() {
let prefs = Prefs::default();
assert_eq!(prefs.theme_name(), "default");
let prefs = Prefs {
theme: Some("dracula".to_string()),
..Default::default()
};
assert_eq!(prefs.theme_name(), "dracula");
}
}