use std::{ env::current_dir, fs::{self, File}, io::Read, path::PathBuf, sync::Arc, }; use once_cell::sync::OnceCell; use paris::info; use serde::{Deserialize, Serialize}; use tauri::{api::path::config_dir, async_runtime::Mutex}; use super::DisplayConfig; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Configuration { pub config_version: u8, pub display_configs: Vec, } impl Configuration { pub fn default() -> Self { Self { config_version: 1, display_configs: vec![], } } } pub struct Manager { config: Arc>, } impl Manager { pub fn global() -> &'static Manager { static DISPLAY_CONFIG_MANAGE: OnceCell = OnceCell::new(); DISPLAY_CONFIG_MANAGE.get_or_init(|| Self::init_from_disk()) } pub fn default() -> Self { Self::new(Configuration::default()) } pub fn new(config: Configuration) -> Self { Self { config: Arc::new(Mutex::new(config)), } } pub fn get_config_file_path() -> PathBuf { config_dir() .unwrap_or(current_dir().unwrap()) .join("display_config.json") } pub fn init_from_disk() -> Self { let config_file_path = Self::get_config_file_path(); match Self::read_config_from_disk(config_file_path) { Ok(config) => Self::new(config), Err(_) => Self::default(), } } pub fn read_config_from_disk(config_file_path: PathBuf) -> anyhow::Result { let mut file = File::open(config_file_path) .map_err(|error| anyhow::anyhow!("config file is not existed. {}", error))?; let mut contents = String::new(); file.read_to_string(&mut contents) .map_err(|error| anyhow::anyhow!("can not read config file. {}", error))?; serde_json::from_str(&contents) .map_err(|error| anyhow::anyhow!("can not parse config file contents. {}", error)) } pub fn write_config_to_disk( config_file_path: PathBuf, config: &Configuration, ) -> anyhow::Result<()> { let contents = serde_json::to_string(config) .map_err(|error| anyhow::anyhow!("can not serialize config. {}", error))?; info!("contents: {}", contents); fs::write(config_file_path, contents.as_bytes()) .map_err(|error| anyhow::anyhow!("can not write config file. {}", error))?; Ok(()) } pub async fn get_config(&self) -> Configuration { self.config.lock().await.clone() } pub async fn set_config(&self, new_config: &Configuration) { let mut config = self.config.lock().await; *config = new_config.clone(); } pub async fn reload_config(&self) -> anyhow::Result { let mut config = self.config.lock().await; let new_config = Self::read_config_from_disk(Self::get_config_file_path()) .map_err(|err| anyhow::anyhow!("can not reload config. {:?}", err))?; *config = new_config.clone(); return anyhow::Ok(new_config); } } #[cfg(test)] mod tests { use std::fs; use serde_json::json; use test_dir::{DirBuilder, TestDir}; use crate::picker::config::Configuration; #[tokio::test] async fn write_config_to_disk_should_be_successful() { let temp = TestDir::temp().create("config_dir", test_dir::FileType::Dir); let config_file_path = temp.path("config_dir").join("picker.config.json"); let manager = crate::picker::config::manger::Manager::default(); crate::picker::config::manger::Manager::write_config_to_disk( config_file_path.clone(), &Configuration::default(), ) .unwrap(); let contents = fs::read_to_string(config_file_path.clone()).unwrap(); let _config: Configuration = serde_json::from_str(contents.as_str()).unwrap(); } #[test] fn read_config_to_disk_should_be_successful() { let temp = TestDir::temp().create("config_dir", test_dir::FileType::Dir); let config_file_path = temp.path("config_dir").join("picker.config.json"); fs::write( config_file_path.clone(), json!({ "config_version": 1, "display_configs": [] }) .to_string() .as_bytes(), ) .unwrap(); let _manager = crate::picker::config::manger::Manager::read_config_from_disk(config_file_path.clone()) .unwrap(); } }