You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

134 lines
3.6 KiB
Rust

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

use std::{
collections::BTreeMap,
env,
fs,
path::{Path, PathBuf},
sync::{Mutex, OnceLock},
};
use serde_json::{Map, Value};
const APP_NAME: &str = "com.ziyun.noticeclient";
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<Value, String> {
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::<Value>(&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}"))
}
static CONFIG_IO_MUTEX: OnceLock<Mutex<()>> = OnceLock::new();
fn lock_config_io() -> Result<std::sync::MutexGuard<'static, ()>, String> {
let mutex = CONFIG_IO_MUTEX.get_or_init(|| Mutex::new(()));
mutex
.lock()
.map_err(|_| "配置文件锁异常poisoned".to_string())
}
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<BTreeMap<String, Value>, String> {
let _guard = lock_config_io()?;
let value = read_config()?;
let Value::Object(map) = value else {
return Ok(BTreeMap::new());
};
Ok(map.into_iter().collect())
}
pub fn merge_config_disk(partial: Value) -> Result<(), String> {
let _guard = lock_config_io()?;
let mut current = read_config()?;
merge_value(&mut current, partial);
write_config(&current)?;
Ok(())
}
#[tauri::command]
pub fn config_merge(partial: Value) -> Result<BTreeMap<String, Value>, String> {
let _guard = lock_config_io()?;
let mut current = read_config()?;
merge_value(&mut current, partial);
write_config(&current)?;
let Value::Object(map) = current else {
return Ok(BTreeMap::new());
};
Ok(map.into_iter().collect())
}