1use std::path::{Path, PathBuf};
12
13pub const APP_DIR: &str = "linkmarks";
15
16pub const STORE_FILENAME: &str = "store.db";
18
19pub const CONFIG_FILENAME: &str = "config.toml";
21
22#[must_use]
27pub fn linkmarks_data_dir() -> PathBuf {
28 if let Some(xdg) = std::env::var_os("XDG_DATA_HOME") {
29 let trimmed = xdg.to_string_lossy().trim().to_string();
30 if !trimmed.is_empty() {
31 return PathBuf::from(trimmed).join(APP_DIR);
32 }
33 }
34 let base = dirs::data_dir().unwrap_or_else(|| PathBuf::from(".local/share"));
35 base.join(APP_DIR)
36}
37
38#[must_use]
43pub fn linkmarks_config_dir() -> PathBuf {
44 if let Some(xdg) = std::env::var_os("XDG_CONFIG_HOME") {
45 let trimmed = xdg.to_string_lossy().trim().to_string();
46 if !trimmed.is_empty() {
47 return PathBuf::from(trimmed).join(APP_DIR);
48 }
49 }
50 let base = dirs::config_dir().unwrap_or_else(|| PathBuf::from(".config"));
51 base.join(APP_DIR)
52}
53
54#[must_use]
56pub fn linkmarks_store_path() -> PathBuf {
57 linkmarks_data_dir().join(STORE_FILENAME)
58}
59
60#[must_use]
62pub fn linkmarks_config_path() -> PathBuf {
63 linkmarks_config_dir().join(CONFIG_FILENAME)
64}
65
66pub fn ensure_data_dir() -> std::io::Result<PathBuf> {
68 let dir = linkmarks_data_dir();
69 std::fs::create_dir_all(&dir)?;
70 Ok(dir)
71}
72
73pub fn ensure_config_dir() -> std::io::Result<PathBuf> {
75 let dir = linkmarks_config_dir();
76 std::fs::create_dir_all(&dir)?;
77 Ok(dir)
78}
79
80#[must_use]
84pub fn default_data_dir_display() -> String {
85 linkmarks_data_dir().to_string_lossy().into_owned()
86}
87
88#[must_use]
92pub fn default_config_dir_display() -> String {
93 linkmarks_config_dir().to_string_lossy().into_owned()
94}
95
96#[must_use]
98pub fn is_inside_data_dir(path: &Path) -> bool {
99 path.starts_with(linkmarks_data_dir())
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn store_path_lives_under_data_dir() {
108 let store = linkmarks_store_path();
109 assert!(store.ends_with(STORE_FILENAME));
110 assert!(is_inside_data_dir(&store));
111 }
112
113 #[test]
114 fn config_path_lives_under_config_dir() {
115 let cfg = linkmarks_config_path();
116 assert!(cfg.ends_with(CONFIG_FILENAME));
117 assert!(cfg.starts_with(linkmarks_config_dir()));
118 }
119
120 #[test]
121 fn data_and_config_dirs_are_distinct() {
122 assert_ne!(linkmarks_data_dir(), linkmarks_config_dir());
123 }
124
125 #[test]
126 fn ensure_data_dir_is_idempotent() {
127 let dir = ensure_data_dir().expect("create data dir");
128 assert!(dir.is_dir());
129 let again = ensure_data_dir().expect("idempotent create");
131 assert_eq!(dir, again);
132 }
133
134 #[test]
135 fn ensure_config_dir_is_idempotent() {
136 let dir = ensure_config_dir().expect("create config dir");
137 assert!(dir.is_dir());
138 let again = ensure_config_dir().expect("idempotent create");
139 assert_eq!(dir, again);
140 }
141}