Skip to main content

cargowatch_core/
paths.rs

1//! Filesystem path discovery for config, data, and logs.
2
3use std::env;
4use std::path::{Path, PathBuf};
5
6use anyhow::{Context, Result};
7use directories::ProjectDirs;
8
9/// Resolved on-disk locations used by the application.
10#[derive(Debug, Clone)]
11pub struct AppPaths {
12    /// Optional synthetic root used for tests and local overrides.
13    pub root: Option<PathBuf>,
14    /// Directory containing config files.
15    pub config_dir: PathBuf,
16    /// Directory containing data files.
17    pub data_dir: PathBuf,
18    /// Directory containing log files.
19    pub log_dir: PathBuf,
20    /// Main config file.
21    pub config_file: PathBuf,
22    /// SQLite database path.
23    pub database_path: PathBuf,
24}
25
26impl AppPaths {
27    /// Resolve the app paths for the current environment.
28    pub fn discover(database_override: Option<PathBuf>) -> Result<Self> {
29        if let Ok(root) = env::var("CARGOWATCH_HOME") {
30            let root = PathBuf::from(root);
31            let config_dir = root.join("config");
32            let data_dir = root.join("data");
33            let log_dir = root.join("logs");
34            let config_file = config_dir.join("config.toml");
35            let database_path = database_override.unwrap_or_else(|| data_dir.join("cargowatch.db"));
36            return Ok(Self {
37                root: Some(root),
38                config_dir,
39                data_dir,
40                log_dir,
41                config_file,
42                database_path,
43            });
44        }
45
46        let project_dirs = ProjectDirs::from("dev", "cargowatch", "CargoWatch")
47            .context("failed to resolve application directories")?;
48        let config_dir = project_dirs.config_dir().to_path_buf();
49        let data_dir = project_dirs.data_local_dir().to_path_buf();
50        let log_dir = data_dir.join("logs");
51        let config_file = config_dir.join("config.toml");
52        let database_path = database_override.unwrap_or_else(|| data_dir.join("cargowatch.db"));
53
54        Ok(Self {
55            root: None,
56            config_dir,
57            data_dir,
58            log_dir,
59            config_file,
60            database_path,
61        })
62    }
63
64    /// Ensure the required directories exist.
65    pub fn ensure_exists(&self) -> Result<()> {
66        std::fs::create_dir_all(&self.config_dir)
67            .with_context(|| format!("failed to create {}", self.config_dir.display()))?;
68        std::fs::create_dir_all(&self.data_dir)
69            .with_context(|| format!("failed to create {}", self.data_dir.display()))?;
70        std::fs::create_dir_all(&self.log_dir)
71            .with_context(|| format!("failed to create {}", self.log_dir.display()))?;
72        Ok(())
73    }
74
75    /// Return the config file path as a generic path reference.
76    pub fn config_file(&self) -> &Path {
77        &self.config_file
78    }
79}