use color_eyre::eyre::Error; use std::collections::HashMap; use tracing::info; #[derive(Clone, PartialEq)] pub struct NativeConfigSystem { config_path: std::path::PathBuf, } impl super::ConfigSystemInterface for NativeConfigSystem { fn new() -> color_eyre::Result { return Ok(NativeConfigSystem { config_path: get_config_path()?, }); } fn config_get(&self, key: &str) -> Option where T: serde::de::DeserializeOwned, { let config = load_config_map(&self.config_path); let Some(value_untyped) = config.get(key).cloned().or_else(|| config_get_default(key)) else { return None; }; match serde_json::from_value::(value_untyped) { Ok(v) => Some(v), Err(_) => { let default_value = config_get_default(key)?; serde_json::from_value::(default_value).ok() } } } fn config_set(&self, key: &str, value: &T) where T: serde::Serialize, { let mut config = load_config_map(&self.config_path); let json_value = serde_json::to_value(value).expect("failed to serialize config value"); config.insert(key.to_string(), json_value); save_config_map(&config).expect("failed to set config") } } #[cfg(any(feature = "desktop"))] fn get_config_path() -> color_eyre::Result { use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs}; let strategy = choose_app_strategy(AppStrategyArgs { top_level_domain: "xyz".to_string(), author: "ohea".to_string(), app_name: "Mumble Web2".to_string(), }) .expect("failed to choose app strategy"); Ok(strategy.config_dir().join("config.json")) } #[cfg(target_os = "android")] fn get_config_path() -> color_eyre::Result { let ctx = ndk_context::android_context(); let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; let mut env = vm.attach_current_thread()?; let ctx = unsafe { jni::objects::JObject::from_raw(ctx.context().cast()) }; let cache_dir = env .call_method(ctx, "getFilesDir", "()Ljava/io/File;", &[])? .l()?; let cache_dir: jni::objects::JString = env .call_method(&cache_dir, "toString", "()Ljava/lang/String;", &[])? .l()? .try_into()?; let cache_dir = env.get_string(&cache_dir)?; let cache_dir = cache_dir.to_str()?; Ok(std::path::PathBuf::from(cache_dir).join("config.json")) } fn load_config_map(config_path: &std::path::PathBuf) -> HashMap { match std::fs::read_to_string(config_path) { Ok(contents) => serde_json::from_str(&contents).unwrap_or_default(), Err(_) => HashMap::new(), } } fn save_config_map(config: &HashMap) -> color_eyre::Result<()> { let config_path = get_config_path().expect("Could not get config file path."); if let Some(parent) = config_path.parent() { info!("Creating config directory: {}", parent.display()); std::fs::create_dir_all(parent)?; } let contents = serde_json::to_string_pretty(config)?; info!("Writing config to {}", config_path.display()); std::fs::write(&config_path, contents)?; Ok(()) } fn config_get_default(key: &str) -> Option { let default_config = platform_default_config(); default_config .get(key) .cloned() .or(super::global_default_config().get(key).cloned()) } fn platform_default_config() -> HashMap { serde_json::json!({}) .as_object() .unwrap() .clone() .into_iter() .collect() }