1use anyhow::{anyhow, Context, Result};
4use std::path::PathBuf;
5
6use crate::detect::Kind;
7
8const ENV_OVERRIDE: &str = "BROWSER_CONTROL_DATA_DIR";
9const CONFIG_ENV_OVERRIDE: &str = "BROWSER_CONTROL_CONFIG_DIR";
10
11pub fn data_dir() -> Result<PathBuf> {
19 if let Some(v) = std::env::var_os(ENV_OVERRIDE) {
20 let p = PathBuf::from(v);
21 if p.as_os_str().is_empty() {
22 return Err(anyhow!("{} is set but empty", ENV_OVERRIDE));
23 }
24 if !p.is_absolute() {
25 return Err(anyhow!(
26 "{} must be an absolute path (got {})",
27 ENV_OVERRIDE,
28 p.display()
29 ));
30 }
31 return Ok(p);
32 }
33
34 let pd = directories::ProjectDirs::from("", "", "browser-control")
35 .ok_or_else(|| anyhow!("could not determine user data directory (no home dir found)"))?;
36 Ok(pd.data_dir().to_path_buf())
37}
38
39pub fn registry_db_path() -> Result<PathBuf> {
41 let dir = data_dir()?;
42 std::fs::create_dir_all(&dir)
43 .with_context(|| format!("creating data dir {}", dir.display()))?;
44 Ok(dir.join("registry.db"))
45}
46
47pub fn config_dir() -> Result<PathBuf> {
55 if let Some(v) = std::env::var_os(CONFIG_ENV_OVERRIDE) {
56 let p = PathBuf::from(v);
57 if p.as_os_str().is_empty() {
58 return Err(anyhow!("{} is set but empty", CONFIG_ENV_OVERRIDE));
59 }
60 if !p.is_absolute() {
61 return Err(anyhow!(
62 "{} must be an absolute path (got {})",
63 CONFIG_ENV_OVERRIDE,
64 p.display()
65 ));
66 }
67 return Ok(p);
68 }
69
70 let pd = directories::ProjectDirs::from("", "", "browser-control")
71 .ok_or_else(|| anyhow!("could not determine user config directory (no home dir found)"))?;
72 Ok(pd.config_dir().to_path_buf())
73}
74
75pub fn config_file_path() -> Result<PathBuf> {
77 let dir = config_dir()?;
78 std::fs::create_dir_all(&dir)
79 .with_context(|| format!("creating config dir {}", dir.display()))?;
80 Ok(dir.join("config.toml"))
81}
82
83pub fn profiles_dir() -> Result<PathBuf> {
85 let dir = data_dir()?.join("profiles");
86 std::fs::create_dir_all(&dir)
87 .with_context(|| format!("creating profiles dir {}", dir.display()))?;
88 Ok(dir)
89}
90
91pub fn default_profile_dir(kind: Kind) -> Result<PathBuf> {
107 let dir = config_dir()?
108 .join("profiles")
109 .join(kind.as_str())
110 .join("default");
111 std::fs::create_dir_all(&dir)
112 .with_context(|| format!("creating default profile dir {}", dir.display()))?;
113 Ok(dir)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn paths_work() {
122 let _g = crate::test_support::ENV_LOCK
123 .lock()
124 .unwrap_or_else(|e| e.into_inner());
125 let tmp = tempfile::TempDir::new().unwrap();
127 let tmp_path = tmp.path().to_path_buf();
128 std::env::set_var(ENV_OVERRIDE, &tmp_path);
131
132 assert_eq!(data_dir().unwrap(), tmp_path);
133
134 let db = registry_db_path().unwrap();
135 assert_eq!(db, tmp_path.join("registry.db"));
136 assert!(db.parent().unwrap().exists());
137 assert_eq!(db.file_name().unwrap(), "registry.db");
138
139 let pdir = profiles_dir().unwrap();
140 assert_eq!(pdir, tmp_path.join("profiles"));
141 assert!(pdir.is_dir());
142 assert_eq!(pdir.file_name().unwrap(), "profiles");
143
144 std::env::remove_var(ENV_OVERRIDE);
146 let d = data_dir().unwrap();
147 let expected_suffix = if cfg!(windows) {
148 "browser-control\\data"
149 } else {
150 "browser-control"
151 };
152 assert!(
153 d.ends_with(expected_suffix),
154 "expected default data_dir to end with {expected_suffix:?}, got {}",
155 d.display()
156 );
157 }
158
159 #[test]
160 fn config_paths_work() {
161 let _g = crate::test_support::ENV_LOCK
162 .lock()
163 .unwrap_or_else(|e| e.into_inner());
164 let tmp = tempfile::TempDir::new().unwrap();
165 let tmp_path = tmp.path().to_path_buf();
166 std::env::set_var(CONFIG_ENV_OVERRIDE, &tmp_path);
167
168 assert_eq!(config_dir().unwrap(), tmp_path);
169
170 let cfg = config_file_path().unwrap();
171 assert_eq!(cfg, tmp_path.join("config.toml"));
172 assert!(cfg.parent().unwrap().exists());
173 assert_eq!(cfg.file_name().unwrap(), "config.toml");
174
175 std::env::remove_var(CONFIG_ENV_OVERRIDE);
176 let d = config_dir().unwrap();
177 let expected_suffix = if cfg!(windows) {
178 "browser-control\\config"
179 } else {
180 "browser-control"
181 };
182 assert!(
183 d.ends_with(expected_suffix),
184 "expected default config_dir to end with {expected_suffix:?}, got {}",
185 d.display()
186 );
187 }
188
189 #[test]
190 fn default_profile_dir_honours_config_env_override() {
191 let _g = crate::test_support::ENV_LOCK
192 .lock()
193 .unwrap_or_else(|e| e.into_inner());
194 let tmp = tempfile::TempDir::new().unwrap();
195 let tmp_path = tmp.path().to_path_buf();
196 std::env::set_var(CONFIG_ENV_OVERRIDE, &tmp_path);
197
198 for k in [
199 Kind::Chrome,
200 Kind::Edge,
201 Kind::Chromium,
202 Kind::Brave,
203 Kind::Firefox,
204 ] {
205 let p = default_profile_dir(k).unwrap();
206 assert_eq!(
207 p,
208 tmp_path.join("profiles").join(k.as_str()).join("default")
209 );
210 assert!(p.is_dir(), "expected {} to be created", p.display());
211 }
212
213 std::env::remove_var(CONFIG_ENV_OVERRIDE);
214 }
215}