darpan 0.2.5

Linux developer service monitoring utility with auto-detection, real-time health checks, and interactive TUI for databases, APIs, Docker containers, and more
Documentation
use crate::packs::schema::{ServicePack, ServicePackSignature};
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use tracing::{debug, info, warn};

/// Loads service packs from user and project directories
pub struct ServicePackLoader;

impl ServicePackLoader {
    /// Load all service packs from user and project directories
    pub fn load_all(project_path: &Path) -> Result<Vec<ServicePack>> {
        let mut packs = Vec::new();

        // Load user-wide packs
        if let Ok(user_packs) = Self::load_user_packs() {
            packs.extend(user_packs);
        }

        // Load project-local packs
        if let Ok(project_packs) = Self::load_project_packs(project_path) {
            packs.extend(project_packs);
        }

        info!("Loaded {} service pack(s)", packs.len());
        Ok(packs)
    }

    /// Load service packs from user config directory
    fn load_user_packs() -> Result<Vec<ServicePack>> {
        let packs_dir = Self::user_packs_dir()?;

        if !packs_dir.exists() {
            return Ok(Vec::new());
        }

        Self::load_packs_from_dir(&packs_dir)
    }

    /// Load service packs from project directory
    fn load_project_packs(project_path: &Path) -> Result<Vec<ServicePack>> {
        let packs_dir = project_path.join(".darpan.d").join("packs");

        if !packs_dir.exists() {
            return Ok(Vec::new());
        }

        Self::load_packs_from_dir(&packs_dir)
    }

    /// Load all YAML files from a directory as service packs
    fn load_packs_from_dir(dir: &Path) -> Result<Vec<ServicePack>> {
        let mut packs = Vec::new();

        let entries = fs::read_dir(dir)
            .with_context(|| format!("Failed to read packs directory: {:?}", dir))?;

        for entry in entries {
            let entry = entry?;
            let path = entry.path();

            if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("yml") {
                match Self::load_pack_from_file(&path) {
                    Ok(pack) => {
                        debug!("Loaded service pack: {} v{}", pack.name, pack.version);
                        packs.push(pack);
                    }
                    Err(e) => {
                        warn!("Failed to load pack from {:?}: {}", path, e);
                    }
                }
            }
        }

        Ok(packs)
    }

    /// Load a single service pack from a YAML file
    fn load_pack_from_file(path: &Path) -> Result<ServicePack> {
        let content = fs::read_to_string(path)
            .with_context(|| format!("Failed to read pack file: {:?}", path))?;

        let pack: ServicePack = serde_yaml::from_str(&content)
            .with_context(|| format!("Failed to parse pack file: {:?}", path))?;

        Ok(pack)
    }

    /// Get user packs directory path
    pub fn user_packs_dir() -> Result<PathBuf> {
        let config_dir = dirs::config_dir()
            .context("Could not determine config directory")?
            .join("darpan")
            .join("packs");

        // Create directory if it doesn't exist
        fs::create_dir_all(&config_dir)?;

        Ok(config_dir)
    }

    /// Get project packs directory path
    pub fn project_packs_dir(project_path: &Path) -> PathBuf {
        project_path.join(".darpan.d").join("packs")
    }

    /// Merge pack signatures into a lookup map
    pub fn build_signature_map(packs: &[ServicePack]) -> HashMap<String, Vec<&ServicePackSignature>> {
        let mut map: HashMap<String, Vec<&ServicePackSignature>> = HashMap::new();

        for pack in packs {
            for signature in &pack.signatures {
                // Index by port if available
                if let Some(port) = signature.port {
                    map.entry(format!("port:{}", port))
                        .or_insert_with(Vec::new)
                        .push(signature);
                }

                // Index by process pattern if available
                if let Some(ref pattern) = signature.process_pattern {
                    map.entry(format!("process:{}", pattern))
                        .or_insert_with(Vec::new)
                        .push(signature);
                }

                // Index by systemd unit if available
                if let Some(ref unit) = signature.systemd_unit {
                    map.entry(format!("systemd:{}", unit))
                        .or_insert_with(Vec::new)
                        .push(signature);
                }

                // Index by docker image if available
                if let Some(ref image) = signature.docker_image {
                    map.entry(format!("docker_image:{}", image))
                        .or_insert_with(Vec::new)
                        .push(signature);
                }
            }
        }

        map
    }
}