Files
owlry/crates/owlry-core/src/paths.rs

218 lines
7.2 KiB
Rust

//! Centralized path handling following XDG Base Directory Specification.
//!
//! XDG directories used:
//! - `$XDG_CONFIG_HOME/owlry/` - User configuration (config.toml, themes/, style.css)
//! - `$XDG_DATA_HOME/owlry/` - User data (scripts/, frecency.json)
//! - `$XDG_CACHE_HOME/owlry/` - Cache files (future use)
//!
//! See: https://specifications.freedesktop.org/basedir-spec/latest/
use std::path::PathBuf;
/// Application name used in XDG paths
const APP_NAME: &str = "owlry";
// =============================================================================
// XDG Base Directories
// =============================================================================
/// Get XDG config home: `$XDG_CONFIG_HOME` or `~/.config`
pub fn config_home() -> Option<PathBuf> {
dirs::config_dir()
}
/// Get XDG data home: `$XDG_DATA_HOME` or `~/.local/share`
pub fn data_home() -> Option<PathBuf> {
dirs::data_dir()
}
/// Get XDG cache home: `$XDG_CACHE_HOME` or `~/.cache`
#[allow(dead_code)]
pub fn cache_home() -> Option<PathBuf> {
dirs::cache_dir()
}
// =============================================================================
// Owlry-specific directories
// =============================================================================
/// Owlry config directory: `$XDG_CONFIG_HOME/owlry/`
pub fn owlry_config_dir() -> Option<PathBuf> {
config_home().map(|p| p.join(APP_NAME))
}
/// Owlry data directory: `$XDG_DATA_HOME/owlry/`
pub fn owlry_data_dir() -> Option<PathBuf> {
data_home().map(|p| p.join(APP_NAME))
}
/// Owlry cache directory: `$XDG_CACHE_HOME/owlry/`
#[allow(dead_code)]
pub fn owlry_cache_dir() -> Option<PathBuf> {
cache_home().map(|p| p.join(APP_NAME))
}
// =============================================================================
// Config files
// =============================================================================
/// Main config file: `$XDG_CONFIG_HOME/owlry/config.toml`
pub fn config_file() -> Option<PathBuf> {
owlry_config_dir().map(|p| p.join("config.toml"))
}
/// Custom user stylesheet: `$XDG_CONFIG_HOME/owlry/style.css`
pub fn custom_style_file() -> Option<PathBuf> {
owlry_config_dir().map(|p| p.join("style.css"))
}
/// User themes directory: `$XDG_CONFIG_HOME/owlry/themes/`
pub fn themes_dir() -> Option<PathBuf> {
owlry_config_dir().map(|p| p.join("themes"))
}
/// Get path for a specific theme: `$XDG_CONFIG_HOME/owlry/themes/{name}.css`
pub fn theme_file(name: &str) -> Option<PathBuf> {
themes_dir().map(|p| p.join(format!("{}.css", name)))
}
// =============================================================================
// Data files
// =============================================================================
/// User plugins directory: `$XDG_CONFIG_HOME/owlry/plugins/`
///
/// Plugins are stored in config because they contain user-installed code
/// that the user explicitly chose to add (similar to themes).
pub fn plugins_dir() -> Option<PathBuf> {
owlry_config_dir().map(|p| p.join("plugins"))
}
/// Frecency data file: `$XDG_DATA_HOME/owlry/frecency.json`
pub fn frecency_file() -> Option<PathBuf> {
owlry_data_dir().map(|p| p.join("frecency.json"))
}
// =============================================================================
// System directories
// =============================================================================
/// System data directories for applications (XDG_DATA_DIRS)
///
/// Follows the XDG Base Directory Specification:
/// - $XDG_DATA_HOME/applications (defaults to ~/.local/share/applications)
/// - $XDG_DATA_DIRS/*/applications (defaults to /usr/local/share:/usr/share)
/// - Additional Flatpak and Snap directories
pub fn system_data_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();
let mut seen = std::collections::HashSet::new();
// Helper to add unique directories
let mut add_dir = |path: PathBuf| {
if seen.insert(path.clone()) {
dirs.push(path);
}
};
// 1. User data directory first (highest priority)
if let Some(data) = data_home() {
add_dir(data.join("applications"));
}
// 2. XDG_DATA_DIRS - parse the environment variable
// Default per spec: /usr/local/share:/usr/share
let xdg_data_dirs = std::env::var("XDG_DATA_DIRS")
.unwrap_or_else(|_| "/usr/local/share:/usr/share".to_string());
for dir in xdg_data_dirs.split(':') {
if !dir.is_empty() {
add_dir(PathBuf::from(dir).join("applications"));
}
}
// 3. Always include standard system directories as fallback
// Some environments set XDG_DATA_DIRS without including these
add_dir(PathBuf::from("/usr/share/applications"));
add_dir(PathBuf::from("/usr/local/share/applications"));
// 4. Flatpak directories (user and system)
if let Some(data) = data_home() {
add_dir(data.join("flatpak/exports/share/applications"));
}
add_dir(PathBuf::from("/var/lib/flatpak/exports/share/applications"));
// 5. Snap directories
add_dir(PathBuf::from("/var/lib/snapd/desktop/applications"));
// 6. Nix directories (common on NixOS)
if let Some(home) = dirs::home_dir() {
add_dir(home.join(".nix-profile/share/applications"));
}
add_dir(PathBuf::from("/run/current-system/sw/share/applications"));
dirs
}
// =============================================================================
// Runtime files
// =============================================================================
/// IPC socket path: `$XDG_RUNTIME_DIR/owlry/owlry.sock`
///
/// Falls back to `/tmp` if `$XDG_RUNTIME_DIR` is not set.
pub fn socket_path() -> PathBuf {
let runtime_dir = std::env::var("XDG_RUNTIME_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("/tmp"));
runtime_dir.join(APP_NAME).join("owlry.sock")
}
// =============================================================================
// Helper functions
// =============================================================================
/// Ensure parent directory of a file exists
pub fn ensure_parent_dir(path: &std::path::Path) -> std::io::Result<()> {
if let Some(parent) = path.parent()
&& !parent.exists()
{
std::fs::create_dir_all(parent)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_paths_are_consistent() {
// All owlry paths should be under XDG directories
if let (Some(config), Some(data)) = (owlry_config_dir(), owlry_data_dir()) {
assert!(config.ends_with("owlry"));
assert!(data.ends_with("owlry"));
}
}
#[test]
fn test_config_file_path() {
if let Some(path) = config_file() {
assert!(path.ends_with("config.toml"));
assert!(path.to_string_lossy().contains("owlry"));
}
}
#[test]
fn test_frecency_in_data_dir() {
if let Some(path) = frecency_file() {
assert!(path.ends_with("frecency.json"));
// Should be in data dir, not config dir
let path_str = path.to_string_lossy();
assert!(
path_str.contains(".local/share") || path_str.contains("XDG_DATA_HOME"),
"frecency should be in data directory"
);
}
}
}