proses 0.1.1

Proses – Professional Secure Execution System
use crate::process::Process;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, fs, path::PathBuf};

/// Persistent state for all managed processes.
///
/// Serialised as pretty-printed JSON to `~/.proses/store.json`.
/// A `BTreeMap` is used so that processes are always iterated in
/// insertion (ID) order — important for stable `list` output.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Store {
    /// Counter used to assign the next unique process ID.
    pub next_id: u32,

    /// All known processes, keyed by their unique ID.
    pub processes: BTreeMap<u32, Process>,
}

impl Store {
    // ---------------------------------------------------------------
    // Persistence
    // ---------------------------------------------------------------

    /// Load the store from disk.  Returns an empty `Store` on any error
    /// (missing file, corrupt JSON, etc.) so the daemon can always
    /// start cleanly.
    pub fn load(path: &PathBuf) -> Self {
        if path.exists() {
            if let Ok(content) = fs::read_to_string(path) {
                if let Ok(store) = serde_json::from_str::<Self>(&content) {
                    return store;
                }
            }
        }
        Self::default()
    }

    /// Persist the store to disk atomically (write to a temp file, then
    /// rename) so a crash during the write never leaves a corrupt state.
    pub fn save(&self, path: &PathBuf) -> Result<()> {
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }

        let tmp_path = path.with_extension("json.tmp");
        let content = serde_json::to_string_pretty(self)?;
        fs::write(&tmp_path, &content)?;
        fs::rename(&tmp_path, path)?;
        Ok(())
    }

    // ---------------------------------------------------------------
    // ID management
    // ---------------------------------------------------------------

    /// Return the next available ID and advance the counter.
    pub fn alloc_id(&mut self) -> u32 {
        let id = self.next_id;
        self.next_id += 1;
        id
    }

    // ---------------------------------------------------------------
    // Lookup helpers
    // ---------------------------------------------------------------

    /// Resolve a "name or numeric ID" string to a process ID.
    ///
    /// Numeric strings are tried as an ID first; if that entry does not
    /// exist the string is also tried as a name, so a process literally
    /// named `"0"` is still reachable by name.
    pub fn find_id(&self, name_or_id: &str) -> Option<u32> {
        // Fast-path: numeric look-up by ID.
        if let Ok(id) = name_or_id.parse::<u32>() {
            if self.processes.contains_key(&id) {
                return Some(id);
            }
        }
        // Fall back to name search.
        self.processes
            .values()
            .find(|p| p.name == name_or_id)
            .map(|p| p.id)
    }

    /// Immutable look-up by name or numeric ID.
    pub fn find(&self, name_or_id: &str) -> Option<&Process> {
        self.find_id(name_or_id)
            .and_then(|id| self.processes.get(&id))
    }
}