cargowatch-core 0.1.0

Shared domain types, configuration, and session state for CargoWatch.
Documentation
//! Filesystem path discovery for config, data, and logs.

use std::env;
use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use directories::ProjectDirs;

/// Resolved on-disk locations used by the application.
#[derive(Debug, Clone)]
pub struct AppPaths {
    /// Optional synthetic root used for tests and local overrides.
    pub root: Option<PathBuf>,
    /// Directory containing config files.
    pub config_dir: PathBuf,
    /// Directory containing data files.
    pub data_dir: PathBuf,
    /// Directory containing log files.
    pub log_dir: PathBuf,
    /// Main config file.
    pub config_file: PathBuf,
    /// SQLite database path.
    pub database_path: PathBuf,
}

impl AppPaths {
    /// Resolve the app paths for the current environment.
    pub fn discover(database_override: Option<PathBuf>) -> Result<Self> {
        if let Ok(root) = env::var("CARGOWATCH_HOME") {
            let root = PathBuf::from(root);
            let config_dir = root.join("config");
            let data_dir = root.join("data");
            let log_dir = root.join("logs");
            let config_file = config_dir.join("config.toml");
            let database_path = database_override.unwrap_or_else(|| data_dir.join("cargowatch.db"));
            return Ok(Self {
                root: Some(root),
                config_dir,
                data_dir,
                log_dir,
                config_file,
                database_path,
            });
        }

        let project_dirs = ProjectDirs::from("dev", "cargowatch", "CargoWatch")
            .context("failed to resolve application directories")?;
        let config_dir = project_dirs.config_dir().to_path_buf();
        let data_dir = project_dirs.data_local_dir().to_path_buf();
        let log_dir = data_dir.join("logs");
        let config_file = config_dir.join("config.toml");
        let database_path = database_override.unwrap_or_else(|| data_dir.join("cargowatch.db"));

        Ok(Self {
            root: None,
            config_dir,
            data_dir,
            log_dir,
            config_file,
            database_path,
        })
    }

    /// Ensure the required directories exist.
    pub fn ensure_exists(&self) -> Result<()> {
        std::fs::create_dir_all(&self.config_dir)
            .with_context(|| format!("failed to create {}", self.config_dir.display()))?;
        std::fs::create_dir_all(&self.data_dir)
            .with_context(|| format!("failed to create {}", self.data_dir.display()))?;
        std::fs::create_dir_all(&self.log_dir)
            .with_context(|| format!("failed to create {}", self.log_dir.display()))?;
        Ok(())
    }

    /// Return the config file path as a generic path reference.
    pub fn config_file(&self) -> &Path {
        &self.config_file
    }
}