use anyhow::Result;
use serde::{Serialize, de::DeserializeOwned};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
pub trait ConfigModule: Serialize + DeserializeOwned + Clone + Send + Sync + 'static {
fn module_name() -> &'static str;
fn default() -> Self;
fn validate(&self) -> Result<()> {
Ok(())
}
fn on_load(&mut self) -> Result<()> {
Ok(())
}
fn on_save(&mut self) -> Result<()> {
Ok(())
}
}
#[derive(Clone)]
pub struct ConfigManager {
config_path: PathBuf,
configs: Arc<RwLock<HashMap<String, serde_json::Value>>>,
}
impl ConfigManager {
pub fn new() -> Result<Self> {
let home_dir = dirs::home_dir()
.ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
let config_dir = home_dir.join(".bckplayer");
let config_path = config_dir.join("config.json");
if !config_dir.exists() {
std::fs::create_dir_all(&config_dir)?;
}
let manager = Self {
config_path,
configs: Arc::new(RwLock::new(HashMap::new())),
};
manager.load_all()?;
Ok(manager)
}
pub fn load_all(&self) -> Result<()> {
if !self.config_path.exists() {
self.initialize_defaults()?;
self.save_all()?;
return Ok(());
}
let content = std::fs::read_to_string(&self.config_path)?;
let configs: HashMap<String, serde_json::Value> = if content.trim().is_empty() {
self.initialize_defaults()?;
self.save_all()?;
return Ok(());
} else {
serde_json::from_str(&content)?
};
if let Ok(mut config_lock) = self.configs.write() {
*config_lock = configs;
}
Ok(())
}
pub fn save_all(&self) -> Result<()> {
let configs = self.configs.read()
.map_err(|_| anyhow::anyhow!("Failed to read configs for saving"))?;
let content = serde_json::to_string_pretty(&*configs)?;
std::fs::write(&self.config_path, content)?;
Ok(())
}
pub fn get<T: ConfigModule>(&self) -> T {
let module_name = T::module_name();
let configs = match self.configs.read() {
Ok(configs) => configs,
Err(_) => return T::default(),
};
match configs.get(module_name) {
Some(value) => {
match serde_json::from_value::<T>(value.clone()) {
Ok(mut config) => {
let _ = config.on_load();
config
}
Err(_) => T::default(),
}
}
None => T::default(),
}
}
pub fn set<T: ConfigModule>(&self, mut config: T) -> Result<()> {
let module_name = T::module_name();
config.validate()?;
config.on_save()?;
let value = serde_json::to_value(&config)?;
{
let mut configs = self.configs.write()
.map_err(|_| anyhow::anyhow!("Failed to write configs"))?;
configs.insert(module_name.to_string(), value);
}
self.save_all()?;
Ok(())
}
pub fn update<T: ConfigModule, F>(&self, updater: F) -> Result<()>
where
F: FnOnce(&mut T),
{
let mut config = self.get::<T>();
updater(&mut config);
self.set(config)
}
pub fn has_module<T: ConfigModule>(&self) -> bool {
let module_name = T::module_name();
let configs = match self.configs.read() {
Ok(configs) => configs,
Err(_) => return false,
};
configs.contains_key(module_name)
}
pub fn remove_module<T: ConfigModule>(&self) -> Result<()> {
let module_name = T::module_name();
{
let mut configs = self.configs.write()
.map_err(|_| anyhow::anyhow!("Failed to write configs"))?;
configs.remove(module_name);
}
self.save_all()?;
Ok(())
}
pub fn get_module_names(&self) -> Vec<String> {
let configs = match self.configs.read() {
Ok(configs) => configs,
Err(_) => return Vec::new(),
};
configs.keys().cloned().collect()
}
pub fn sync(&self) -> Result<()> {
self.save_all()
}
pub fn get_config_path(&self) -> &PathBuf {
&self.config_path
}
pub fn get_config_size(&self) -> Result<u64> {
Ok(std::fs::metadata(&self.config_path)?.len())
}
pub fn export_to(&self, path: &PathBuf) -> Result<()> {
let configs = self.configs.read()
.map_err(|_| anyhow::anyhow!("Failed to read configs for export"))?;
let content = serde_json::to_string_pretty(&*configs)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn import_from(&self, path: &PathBuf) -> Result<()> {
let content = std::fs::read_to_string(path)?;
let configs: HashMap<String, serde_json::Value> = serde_json::from_str(&content)?;
{
let mut config_lock = self.configs.write()
.map_err(|_| anyhow::anyhow!("Failed to write configs during import"))?;
*config_lock = configs;
}
self.save_all()?;
Ok(())
}
pub fn reset_all(&self) -> Result<()> {
{
let mut configs = self.configs.write()
.map_err(|_| anyhow::anyhow!("Failed to write configs during reset"))?;
configs.clear();
}
self.save_all()?;
Ok(())
}
fn initialize_defaults(&self) -> Result<()> {
use crate::config::Config;
use crate::config_modules::{YoutubeConfig, NetworkConfig, AppearanceConfig, MediaConfig, AdvancedConfig};
let mut configs = self.configs.write()
.map_err(|_| anyhow::anyhow!("Failed to write configs during initialization"))?;
let player_config = <Config as ConfigModule>::default();
let youtube_config = <YoutubeConfig as ConfigModule>::default();
let network_config = <NetworkConfig as ConfigModule>::default();
let appearance_config = <AppearanceConfig as ConfigModule>::default();
let media_config = <MediaConfig as ConfigModule>::default();
let advanced_config = <AdvancedConfig as ConfigModule>::default();
configs.insert("player".to_string(), serde_json::to_value(player_config)?);
configs.insert("youtube".to_string(), serde_json::to_value(youtube_config)?);
configs.insert("network".to_string(), serde_json::to_value(network_config)?);
configs.insert("appearance".to_string(), serde_json::to_value(appearance_config)?);
configs.insert("media".to_string(), serde_json::to_value(media_config)?);
configs.insert("advanced".to_string(), serde_json::to_value(advanced_config)?);
Ok(())
}
}
impl Default for ConfigManager {
fn default() -> Self {
Self::new().expect("Failed to create default ConfigManager")
}
}