diff --git a/espanso-path/Cargo.toml b/espanso-path/Cargo.toml new file mode 100644 index 0000000..bff0b50 --- /dev/null +++ b/espanso-path/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "espanso-path" +version = "0.1.0" +authors = ["Federico Terzi "] +edition = "2018" + +[dependencies] +log = "0.4.14" +anyhow = "1.0.38" +thiserror = "1.0.23" +dirs = "3.0.1" \ No newline at end of file diff --git a/espanso-path/src/lib.rs b/espanso-path/src/lib.rs new file mode 100644 index 0000000..0f9cc69 --- /dev/null +++ b/espanso-path/src/lib.rs @@ -0,0 +1,282 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use log::{debug, info}; +use std::{ + fs::create_dir_all, + path::{Path, PathBuf}, +}; + +#[derive(Debug, Clone)] +pub struct Paths { + pub config: PathBuf, + pub runtime: PathBuf, + pub packages: PathBuf, +} + +pub fn resolve_paths() -> Paths { + let config_dir = if let Some(config_dir) = get_config_dir() { + config_dir + } else { + // Create the config directory if not already present + let config_dir = get_default_config_path(); + info!("creating config directory in {:?}", config_dir); + create_dir_all(&config_dir).expect("unable to create config directory"); + config_dir + }; + + let runtime_dir = if let Some(runtime_dir) = get_runtime_dir() { + runtime_dir + } else { + // Create the runtime directory if not already present + let runtime_dir = if !is_portable_mode() { + get_default_runtime_path() + } else { + get_portable_runtime_path().expect("unable to obtain runtime directory path") + }; + info!("creating runtime directory in {:?}", runtime_dir); + create_dir_all(&runtime_dir).expect("unable to create runtime directory"); + runtime_dir + }; + + let packages_dir = if let Some(package_dir) = get_packages_dir(&config_dir, &runtime_dir) { + package_dir + } else { + // Create the packages directory if not already present + let packages_dir = get_default_packages_path(&config_dir); + info!("creating packages directory in {:?}", packages_dir); + create_dir_all(&packages_dir).expect("unable to create packages directory"); + packages_dir + }; + + Paths { + config: config_dir, + runtime: runtime_dir, + packages: packages_dir, + } +} + +fn get_config_dir() -> Option { + if let Some(portable_dir) = get_portable_config_dir() { + // Portable mode + debug!("detected portable config directory in {:?}", portable_dir); + Some(portable_dir) + } else if let Some(config_dir) = get_home_espanso_dir() { + // $HOME/.espanso + debug!("detected config directory in $HOME/.espanso"); + Some(config_dir) + } else if let Some(config_dir) = get_home_config_espanso_dir() { + // $HOME/.config/espanso + debug!("detected config directory in $HOME/.config/espanso"); + Some(config_dir) + } else if let Some(legacy_mac_dir) = get_legacy_mac_dir() { + // Legacy macOS location in ~/Library/Preferences/espanso + debug!( + "detected legacy config directory location at {:?}", + legacy_mac_dir + ); + Some(legacy_mac_dir) + } else if let Some(config_dir) = get_default_config_dir() { + debug!("detected default config directory at {:?}", config_dir); + Some(config_dir) + } else { + None + } +} + +fn get_portable_config_dir() -> Option { + let espanso_exe_path = std::env::current_exe().expect("unable to obtain executable path"); + let exe_dir = espanso_exe_path.parent(); + if let Some(parent) = exe_dir { + let config_dir = parent.join(".espanso"); + if config_dir.is_dir() { + return Some(config_dir); + } + } + None +} + +fn get_home_espanso_dir() -> Option { + if let Some(home_dir) = dirs::home_dir() { + let config_espanso_dir = home_dir.join(".espanso"); + if config_espanso_dir.is_dir() { + return Some(config_espanso_dir); + } + } + None +} + +fn get_home_config_espanso_dir() -> Option { + if let Some(home_dir) = dirs::home_dir() { + let home_espanso_dir = home_dir.join(".config").join("espanso"); + if home_espanso_dir.is_dir() { + return Some(home_espanso_dir); + } + } + None +} + +fn get_default_config_dir() -> Option { + let config_path = get_default_config_path(); + if config_path.is_dir() { + return Some(config_path); + } + None +} + +fn get_default_config_path() -> PathBuf { + let config_dir = dirs::config_dir().expect("unable to obtain dirs::config_dir()"); + config_dir.join("espanso") +} + +// Due to the original behavior of the dirs crate, espanso placed the config +// directory in the Preferences folder on macOS, but this is not an optimal +// approach. +// For more context, see: https://github.com/federico-terzi/espanso/issues/611 +fn get_legacy_mac_dir() -> Option { + if cfg!(target_os = "macos") { + if let Some(preferences_dir) = dirs::preference_dir() { + let espanso_dir = preferences_dir.join("espanso"); + if espanso_dir.is_dir() { + return Some(espanso_dir); + } + } + } + None +} + +fn get_runtime_dir() -> Option { + if let Some(runtime_dir) = get_portable_runtime_dir() { + debug!("detected portable runtime dir: {:?}", runtime_dir); + Some(runtime_dir) + } else if let Some(legacy_dir) = get_legacy_runtime_dir() { + debug!("detected legacy runtime dir: {:?}", legacy_dir); + Some(legacy_dir) + } else if let Some(default_dir) = get_default_runtime_dir() { + debug!("detected default runtime dir: {:?}", default_dir); + Some(default_dir) + } else { + None + } +} + +fn get_portable_runtime_dir() -> Option { + if let Some(runtime_dir) = get_portable_runtime_path() { + if runtime_dir.is_dir() { + return Some(runtime_dir); + } + } + None +} + +fn get_portable_runtime_path() -> Option { + let espanso_exe_path = std::env::current_exe().expect("unable to obtain executable path"); + let exe_dir = espanso_exe_path.parent(); + if let Some(parent) = exe_dir { + let config_dir = parent.join(".espanso-runtime"); + return Some(config_dir); + } + None +} + +fn get_legacy_runtime_dir() -> Option { + let data_dir = dirs::data_local_dir().expect("unable to obtain dirs::data_local_dir()"); + let espanso_dir = data_dir.join("espanso"); + if espanso_dir.is_dir() { + Some(espanso_dir) + } else { + None + } +} + +fn get_default_runtime_dir() -> Option { + let default_dir = get_default_runtime_path(); + if default_dir.is_dir() { + Some(default_dir) + } else { + None + } +} + +fn get_default_runtime_path() -> PathBuf { + let runtime_dir = if cfg!(target_os = "linux") { + dirs::runtime_dir().expect("unable to obtain dirs::runtime_dir()") + } else { + dirs::cache_dir().expect("unable to obtain dirs::cache_dir()") + }; + runtime_dir.join("espanso") +} + +fn get_packages_dir(config_dir: &Path, legacy_data_dir: &Path) -> Option { + if let Some(packages_dir) = get_legacy_embedded_packages_dir(config_dir) { + debug!("detected legacy packages dir: {:?}", packages_dir); + Some(packages_dir) + } else if let Some(packages_dir) = get_legacy_packages_dir(legacy_data_dir) { + debug!("detected legacy packages dir: {:?}", packages_dir); + Some(packages_dir) + } else if let Some(packages_dir) = get_default_packages_dir(config_dir) { + debug!("detected default packages dir: {:?}", packages_dir); + Some(packages_dir) + } else { + None + } +} + +fn get_legacy_packages_dir(legacy_data_dir: &Path) -> Option { + let legacy_dir = legacy_data_dir.join("packages"); + if legacy_dir.is_dir() { + Some(legacy_dir) + } else { + None + } +} + +fn get_legacy_embedded_packages_dir(config_dir: &Path) -> Option { + let legacy_dir = config_dir.join("packages"); + if legacy_dir.is_dir() { + Some(legacy_dir) + } else { + None + } +} + +fn get_default_packages_dir(config_dir: &Path) -> Option { + let packages_dir = get_default_packages_path(config_dir); + if packages_dir.is_dir() { + Some(packages_dir) + } else { + None + } +} + +fn get_default_packages_path(config_dir: &Path) -> PathBuf { + config_dir.join("match").join("packages") +} + +fn is_portable_mode() -> bool { + let espanso_exe_path = std::env::current_exe().expect("unable to obtain executable path"); + let exe_dir = espanso_exe_path.parent(); + if let Some(parent) = exe_dir { + let config_dir = parent.join(".espanso"); + if config_dir.is_dir() { + return true; + } + } + false +}