Skip to main content

lingxia_settings/
lib.rs

1use dashmap::DashMap;
2use serde::{Deserialize, Serialize};
3use std::path::{Path, PathBuf};
4use std::sync::OnceLock;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum SettingsError {
9    #[error("I/O error: {0}")]
10    Io(#[from] std::io::Error),
11    #[error("JSON error: {0}")]
12    Json(#[from] serde_json::Error),
13}
14
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct Settings {
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub download_dir: Option<String>,
20}
21
22static SETTINGS_CACHE: OnceLock<DashMap<String, Settings>> = OnceLock::new();
23
24fn cache() -> &'static DashMap<String, Settings> {
25    SETTINGS_CACHE.get_or_init(DashMap::new)
26}
27
28fn settings_key(app_data_dir: &Path) -> String {
29    app_data_dir.to_string_lossy().to_string()
30}
31
32pub fn settings_path(app_data_dir: &Path) -> PathBuf {
33    lingxia_app_context::app_state_file(app_data_dir, "settings.json")
34}
35
36pub fn load(app_data_dir: &Path) -> Result<Settings, SettingsError> {
37    let key = settings_key(app_data_dir);
38    if let Some(entry) = cache().get(&key) {
39        return Ok(entry.value().clone());
40    }
41
42    let path = settings_path(app_data_dir);
43    let settings = match std::fs::read(&path) {
44        Ok(bytes) => serde_json::from_slice::<Settings>(&bytes)?,
45        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Settings::default(),
46        Err(err) => return Err(SettingsError::Io(err)),
47    };
48
49    cache().insert(key, settings.clone());
50    Ok(settings)
51}
52
53pub fn save(app_data_dir: &Path, settings: &Settings) -> Result<(), SettingsError> {
54    let path = settings_path(app_data_dir);
55    if let Some(parent) = path.parent() {
56        std::fs::create_dir_all(parent)?;
57    }
58    let bytes = serde_json::to_vec_pretty(settings)?;
59    std::fs::write(&path, bytes)?;
60    cache().insert(settings_key(app_data_dir), settings.clone());
61    Ok(())
62}
63
64pub fn get_download_dir(app_data_dir: &Path) -> Result<Option<PathBuf>, SettingsError> {
65    Ok(load(app_data_dir)?
66        .download_dir
67        .filter(|value| !value.trim().is_empty())
68        .map(PathBuf::from))
69}
70
71pub fn set_download_dir(
72    app_data_dir: &Path,
73    path: Option<impl AsRef<Path>>,
74) -> Result<(), SettingsError> {
75    let mut settings = load(app_data_dir)?;
76    settings.download_dir = path.map(|value| value.as_ref().to_string_lossy().to_string());
77    save(app_data_dir, &settings)
78}