use dirs::{config_dir, home_dir};
use serde::{Deserialize, Deserializer};
use std::path::Path;
use std::{collections::BTreeMap, fs, path::PathBuf};
use std::{env, result};
use crate::obsidian::{Error, Result, Vault};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ObsidianConfig {
vaults: BTreeMap<String, Vault>,
}
pub fn load() -> Result<ObsidianConfig> {
let config_locations = obsidian_global_config_locations();
let existing_config_locations = config_locations
.iter()
.filter(|path| path.is_dir())
.collect::<Vec<_>>();
if let Some(config_dir) = existing_config_locations.first() {
load_from(config_dir)
} else {
Err(Error::PathNotFound(format!(
"Obsidian config directory was not found from these locations: {}",
config_locations
.iter()
.map(|path| path.to_string_lossy())
.collect::<Vec<_>>()
.join(", ")
)))
}
}
pub fn load_from(config_path: &Path) -> Result<ObsidianConfig> {
let obsidian_json_path = config_path.join("obsidian.json");
if obsidian_json_path.try_exists()? {
let contents = fs::read_to_string(obsidian_json_path)?;
serde_json::from_str(&contents).map_err(Error::Json)
} else {
Err(Error::PathNotFound(
obsidian_json_path.to_string_lossy().to_string(),
))
}
}
impl ObsidianConfig {
pub fn vaults(&self) -> Vec<&Vault> {
self.vaults.values().collect()
}
}
impl<const N: usize> From<[(&str, Vault); N]> for ObsidianConfig {
fn from(arr: [(&str, Vault); N]) -> Self {
Self {
vaults: BTreeMap::from(arr.map(|(name, vault)| (name.to_owned(), vault))),
}
}
}
impl<const N: usize> From<[(String, Vault); N]> for ObsidianConfig {
fn from(arr: [(String, Vault); N]) -> Self {
Self {
vaults: BTreeMap::from(arr),
}
}
}
impl<'de> Deserialize<'de> for ObsidianConfig {
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Json {
vaults: BTreeMap<String, Vault>,
}
impl From<Json> for ObsidianConfig {
fn from(value: Json) -> Self {
ObsidianConfig {
vaults: value
.vaults
.into_values()
.map(|vault| (vault.name.clone(), vault))
.collect(),
}
}
}
let deserialized: Json = Deserialize::deserialize(deserializer)?;
Ok(deserialized.into())
}
}
pub fn obsidian_global_config_locations() -> Vec<PathBuf> {
#[cfg(any(target_os = "macos", target_os = "linux"))]
const OBSIDIAN_CONFIG_DIR_NAME: &str = "obsidian";
#[cfg(target_os = "windows")]
const OBSIDIAN_CONFIG_DIR_NAME: &str = "Obsidian";
let override_path =
env::var("OBSIDIAN_CONFIG_DIR")
.ok()
.zip(home_dir())
.map(|(path, home_dir)| {
PathBuf::from(path.replace("~", home_dir.to_string_lossy().as_ref()))
});
let default_config_path =
config_dir().map(|config_path| config_path.join(OBSIDIAN_CONFIG_DIR_NAME));
#[cfg(any(target_os = "macos", target_os = "windows"))]
let sandboxed_paths: [Option<PathBuf>; 0] = [];
#[cfg(target_os = "linux")]
let sandboxed_paths = {
let flatpak_path = home_dir().map(|home_dir| {
home_dir
.join(".var/app/md.obsidian.Obsidian/config")
.join(OBSIDIAN_CONFIG_DIR_NAME)
});
let snap_path = home_dir().map(|home_dir| {
home_dir
.join("snap/obsidian/current/.config")
.join(OBSIDIAN_CONFIG_DIR_NAME)
});
[flatpak_path, snap_path]
};
let base_paths = [override_path, default_config_path];
base_paths
.into_iter()
.chain(sandboxed_paths)
.flatten()
.collect()
}