use std::{ collections::BTreeMap, env, fs, path::{Path, PathBuf}, }; use serde_json::{Map, Value}; const APP_NAME: &str = "com.ziyun.callclient"; const CONFIG_FILE_NAME: &str = "config.json"; fn get_home_dir() -> PathBuf { if let Ok(value) = env::var("HOME") { return PathBuf::from(value); } if let Ok(value) = env::var("USERPROFILE") { return PathBuf::from(value); } PathBuf::from(".") } fn get_config_dir() -> PathBuf { if cfg!(target_os = "linux") { if let Ok(value) = env::var("XDG_CONFIG_HOME") { return PathBuf::from(value).join(APP_NAME); } return get_home_dir().join(".config").join(APP_NAME); } if let Ok(value) = env::var("APPDATA") { return PathBuf::from(value).join(APP_NAME); } get_home_dir().join(".").join(APP_NAME) } fn get_config_file_path() -> PathBuf { get_config_dir().join(CONFIG_FILE_NAME) } fn ensure_parent_dir(path: &Path) -> Result<(), String> { let Some(parent) = path.parent() else { return Ok(()); }; fs::create_dir_all(parent).map_err(|error| format!("创建配置目录失败: {error}")) } fn read_config() -> Result { let path = get_config_file_path(); if !path.exists() { return Ok(Value::Object(Map::new())); } let content = fs::read_to_string(&path).map_err(|error| format!("读取配置文件失败: {error}"))?; if content.trim().is_empty() { return Ok(Value::Object(Map::new())); } serde_json::from_str::(&content).map_err(|error| format!("解析配置文件失败: {error}")) } fn write_config(value: &Value) -> Result<(), String> { let path = get_config_file_path(); ensure_parent_dir(&path)?; let content = serde_json::to_string_pretty(value).map_err(|error| format!("序列化配置失败: {error}"))?; fs::write(path, content).map_err(|error| format!("写入配置文件失败: {error}")) } fn merge_value(target: &mut Value, patch: Value) { match (target, patch) { (Value::Object(target_map), Value::Object(patch_map)) => { for (key, value) in patch_map { if let Some(existing) = target_map.get_mut(&key) { merge_value(existing, value); } else { target_map.insert(key, value); } } } (target_value, patch_value) => { *target_value = patch_value; } } } #[tauri::command] pub fn config_get_all() -> Result, String> { let value = read_config()?; let Value::Object(map) = value else { return Ok(BTreeMap::new()); }; Ok(map.into_iter().collect()) } #[tauri::command] pub fn config_merge(partial: Value) -> Result, String> { let mut current = read_config()?; merge_value(&mut current, partial); write_config(¤t)?; let Value::Object(map) = current else { return Ok(BTreeMap::new()); }; Ok(map.into_iter().collect()) }