1use origin_domain::{AppError, Result};
9use std::path::PathBuf;
10
11pub const DATA_DIR_ENV: &str = "ORIGIN_DATA_DIR";
14
15pub fn data_dir(app_id: &str) -> Result<PathBuf> {
23 let directory = resolve(
24 app_id,
25 std::env::var_os(DATA_DIR_ENV).map(PathBuf::from),
26 base_dir()?,
27 );
28
29 std::fs::create_dir_all(&directory).map_err(|error| {
30 AppError::storage(format!("cannot create {}: {error}", directory.display()))
31 })?;
32
33 Ok(directory)
34}
35
36fn resolve(app_id: &str, override_path: Option<PathBuf>, base: PathBuf) -> PathBuf {
38 override_path.unwrap_or_else(|| base.join(app_id))
39}
40
41#[cfg(target_os = "macos")]
42fn base_dir() -> Result<PathBuf> {
43 Ok(home()?.join("Library").join("Application Support"))
44}
45
46#[cfg(target_os = "windows")]
47fn base_dir() -> Result<PathBuf> {
48 std::env::var_os("APPDATA")
49 .map(PathBuf::from)
50 .ok_or_else(|| AppError::configuration("APPDATA is not set"))
51}
52
53#[cfg(not(any(target_os = "macos", target_os = "windows")))]
54fn base_dir() -> Result<PathBuf> {
55 match std::env::var_os("XDG_DATA_HOME") {
56 Some(path) => Ok(PathBuf::from(path)),
57 None => Ok(home()?.join(".local").join("share")),
58 }
59}
60
61#[cfg(not(target_os = "windows"))]
62fn home() -> Result<PathBuf> {
63 std::env::var_os("HOME")
64 .map(PathBuf::from)
65 .ok_or_else(|| AppError::configuration("HOME is not set"))
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn an_override_replaces_the_location_entirely() {
74 let resolved = resolve(
75 "dev.origin.test",
76 Some(PathBuf::from("/tmp/portable")),
77 PathBuf::from("/home/user/.local/share"),
78 );
79
80 assert_eq!(resolved, PathBuf::from("/tmp/portable"));
81 }
82
83 #[test]
84 fn the_default_location_carries_the_application_id() {
85 let resolved = resolve("dev.origin.test", None, PathBuf::from("/base"));
86
87 assert_eq!(resolved, PathBuf::from("/base/dev.origin.test"));
88 }
89
90 #[test]
91 fn resolving_creates_the_directory() {
92 let target = std::env::temp_dir().join(format!("origin-paths-{}", std::process::id()));
93 let _ = std::fs::remove_dir_all(&target);
94
95 let resolved = data_dir(target.to_str().expect("utf-8 path"));
96
97 assert!(resolved.expect("resolve").is_dir());
100 }
101}