openlogi_core/paths.rs
1//! Per-OS application directories, following the XDG Base Directory spec on
2//! **every** platform — including macOS, so configuration lives at the
3//! familiar `~/.config/openlogi/` rather than macOS's
4//! `~/Library/Application Support/`.
5//!
6//! | kind | env override | default |
7//! |--------|---------------------|-------------------------------|
8//! | config | `$XDG_CONFIG_HOME` | `~/.config/openlogi` |
9//! | data | `$XDG_DATA_HOME` | `~/.local/share/openlogi` |
10//!
11//! On Windows `$HOME` falls back to `%USERPROFILE%`, so paths resolve to
12//! `%USERPROFILE%\.config\openlogi` etc. — best-effort until a real Windows
13//! port lands.
14
15use std::path::PathBuf;
16
17use etcetera::{BaseStrategy, base_strategy::Xdg};
18use thiserror::Error;
19
20/// Subdirectory created under each XDG base directory.
21const APP_DIR: &str = "openlogi";
22
23#[derive(Debug, Error)]
24pub enum PathsError {
25 #[error("could not resolve a home directory for the current user")]
26 HomeNotFound,
27}
28
29fn xdg() -> Result<Xdg, PathsError> {
30 Xdg::new().map_err(|_| PathsError::HomeNotFound)
31}
32
33/// The raw XDG config home directory (without the `openlogi` subdirectory).
34///
35/// Honours an absolute `$XDG_CONFIG_HOME`; falls back to `~/.config`.
36/// Useful when placing files that belong to other apps under the same base
37/// (e.g. systemd user units at `$XDG_CONFIG_HOME/systemd/user/`).
38pub fn xdg_config_home() -> Result<PathBuf, PathsError> {
39 Ok(xdg()?.config_dir())
40}
41
42/// Directory holding the user's `config.toml`.
43///
44/// `$XDG_CONFIG_HOME/openlogi`, default `~/.config/openlogi`.
45pub fn config_dir() -> Result<PathBuf, PathsError> {
46 Ok(xdg_config_home()?.join(APP_DIR))
47}
48
49/// Full path to the user config file.
50pub fn config_path() -> Result<PathBuf, PathsError> {
51 Ok(config_dir()?.join("config.toml"))
52}
53
54/// Directory for downloaded application data; the device-render asset cache
55/// lives under `data_dir()/assets`.
56///
57/// `$XDG_DATA_HOME/openlogi`, default `~/.local/share/openlogi`.
58pub fn data_dir() -> Result<PathBuf, PathsError> {
59 Ok(xdg()?.data_dir().join(APP_DIR))
60}
61
62/// Directory for runtime sockets — the background agent's IPC endpoint.
63pub fn runtime_dir() -> Result<PathBuf, PathsError> {
64 let xdg = xdg()?;
65 Ok(xdg
66 .runtime_dir()
67 .map_or_else(|| xdg.config_dir().join(APP_DIR), |dir| dir.join(APP_DIR)))
68}
69
70/// Path to the background agent's Unix-domain IPC socket: the GUI connects here
71/// to reach the agent that owns device I/O.
72pub fn agent_socket_path() -> Result<PathBuf, PathsError> {
73 Ok(runtime_dir()?.join("agent.sock"))
74}
75
76#[cfg(all(test, unix))]
77#[allow(clippy::expect_used, reason = "expect/unwrap are idiomatic in tests")]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn config_dir_keeps_openlogi_under_xdg_config_home() {
83 assert!(config_dir().expect("config dir").ends_with("openlogi"));
84 }
85
86 #[test]
87 fn data_dir_keeps_openlogi_under_xdg_data_home() {
88 assert!(data_dir().expect("data dir").ends_with("openlogi"));
89 }
90
91 #[test]
92 fn runtime_dir_keeps_openlogi_suffix() {
93 assert!(runtime_dir().expect("runtime dir").ends_with("openlogi"));
94 }
95}