Skip to main content

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}