main
rs 92 lines 3.14 KB
Raw
1 use crate::process::Process;
2 use anyhow::Result;
3 use serde::{Deserialize, Serialize};
4 use std::{collections::BTreeMap, fs, path::PathBuf};
5
6 /// Persistent state for all managed processes.
7 ///
8 /// Serialised as pretty-printed JSON to `~/.proses/store.json`.
9 /// A `BTreeMap` is used so that processes are always iterated in
10 /// insertion (ID) order — important for stable `list` output.
11 #[derive(Debug, Clone, Serialize, Deserialize, Default)]
12 pub struct Store {
13 /// Counter used to assign the next unique process ID.
14 pub next_id: u32,
15
16 /// All known processes, keyed by their unique ID.
17 pub processes: BTreeMap<u32, Process>,
18 }
19
20 impl Store {
21 // ---------------------------------------------------------------
22 // Persistence
23 // ---------------------------------------------------------------
24
25 /// Load the store from disk. Returns an empty `Store` on any error
26 /// (missing file, corrupt JSON, etc.) so the daemon can always
27 /// start cleanly.
28 pub fn load(path: &PathBuf) -> Self {
29 if path.exists() {
30 if let Ok(content) = fs::read_to_string(path) {
31 if let Ok(store) = serde_json::from_str::<Self>(&content) {
32 return store;
33 }
34 }
35 }
36 Self::default()
37 }
38
39 /// Persist the store to disk atomically (write to a temp file, then
40 /// rename) so a crash during the write never leaves a corrupt state.
41 pub fn save(&self, path: &PathBuf) -> Result<()> {
42 if let Some(parent) = path.parent() {
43 fs::create_dir_all(parent)?;
44 }
45
46 let tmp_path = path.with_extension("json.tmp");
47 let content = serde_json::to_string_pretty(self)?;
48 fs::write(&tmp_path, &content)?;
49 fs::rename(&tmp_path, path)?;
50 Ok(())
51 }
52
53 // ---------------------------------------------------------------
54 // ID management
55 // ---------------------------------------------------------------
56
57 /// Return the next available ID and advance the counter.
58 pub fn alloc_id(&mut self) -> u32 {
59 let id = self.next_id;
60 self.next_id += 1;
61 id
62 }
63
64 // ---------------------------------------------------------------
65 // Lookup helpers
66 // ---------------------------------------------------------------
67
68 /// Resolve a "name or numeric ID" string to a process ID.
69 ///
70 /// Numeric strings are tried as an ID first; if that entry does not
71 /// exist the string is also tried as a name, so a process literally
72 /// named `"0"` is still reachable by name.
73 pub fn find_id(&self, name_or_id: &str) -> Option<u32> {
74 // Fast-path: numeric look-up by ID.
75 if let Ok(id) = name_or_id.parse::<u32>() {
76 if self.processes.contains_key(&id) {
77 return Some(id);
78 }
79 }
80 // Fall back to name search.
81 self.processes
82 .values()
83 .find(|p| p.name == name_or_id)
84 .map(|p| p.id)
85 }
86
87 /// Immutable look-up by name or numeric ID.
88 pub fn find(&self, name_or_id: &str) -> Option<&Process> {
89 self.find_id(name_or_id)
90 .and_then(|id| self.processes.get(&id))
91 }
92 }