kasl/libs/data_storage.rs
1//! Resolves where application files live on each platform.
2//!
3//! ```rust,no_run
4//! # fn main() -> anyhow::Result<()> {
5//! use kasl::libs::data_storage::DataStorage;
6//!
7//! let storage = DataStorage::new();
8//! let db_path = storage.get_path("kasl.db")?;
9//! let config_path = storage.get_path("config.json")?;
10//! # Ok(())
11//! # }
12//! ```
13
14use anyhow::Result;
15use serde::Deserialize;
16use std::env::consts::OS;
17use std::env::var;
18use std::path::{Path, PathBuf};
19use std::{fs, str};
20
21// Include compile-time application metadata
22include!(concat!(env!("OUT_DIR"), "/app_metadata.rs"));
23
24/// The application data directory, resolved once at construction.
25#[derive(Deserialize, Clone)]
26pub struct DataStorage {
27 /// `{platform data dir}/{owner}/{app}`; files are resolved under it.
28 base_path: PathBuf,
29}
30
31impl Default for DataStorage {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl DataStorage {
38 /// Picks the platform data directory and appends owner and app name
39 /// (from compile-time metadata).
40 ///
41 /// `LOCALAPPDATA` on Windows, `~/Library/Application Support` on macOS,
42 /// `~/.local/share` elsewhere; falls back to `.` when the environment
43 /// variable is missing, so restricted environments still run.
44 ///
45 /// ```rust,no_run
46 /// # fn main() -> anyhow::Result<()> {
47 /// use kasl::libs::data_storage::DataStorage;
48 ///
49 /// let storage = DataStorage::new();
50 /// let db_path = storage.get_path("kasl.db")?;
51 /// println!("Database path: {:?}", db_path);
52 /// # Ok(())
53 /// # }
54 /// ```
55 pub fn new() -> Self {
56 let base_path = match OS {
57 "windows" => var("LOCALAPPDATA").unwrap_or_else(|_| ".".into()),
58 "macos" => var("HOME").unwrap_or_else(|_| ".".into()) + "/Library/Application Support",
59 _ => var("HOME").unwrap_or_else(|_| ".".into()) + "/.local/share",
60 };
61
62 let base_path = Path::new(&base_path).join(APP_METADATA_OWNER).join(APP_METADATA_NAME);
63
64 Self { base_path }
65 }
66
67 /// Returns the full path for `file_name` inside the data directory,
68 /// creating the directory tree on first use.
69 ///
70 /// ```rust,no_run
71 /// # fn main() -> anyhow::Result<()> {
72 /// use kasl::libs::data_storage::DataStorage;
73 ///
74 /// let storage = DataStorage::new();
75 ///
76 /// let db_path = storage.get_path("kasl.db")?;
77 /// // /home/user/.local/share/lacodda/kasl/kasl.db (Linux)
78 /// // C:\Users\User\AppData\Local\lacodda\kasl\kasl.db (Windows)
79 ///
80 /// let config_path = storage.get_path("config.json")?;
81 /// let session_path = storage.get_path(".jira_session_id")?;
82 /// # Ok(())
83 /// # }
84 /// ```
85 pub fn get_path(&self, file_name: &str) -> Result<PathBuf> {
86 if !self.base_path.exists() {
87 fs::create_dir_all(&self.base_path)?;
88 }
89 Ok(self.base_path.join(file_name))
90 }
91}