planarian_daemon/
snapshot.rs1use crate::core::{Core, Peer};
11use planarian_ipc::{ConsoleReport, LogLine, Request, Response};
12use planarian_schema::{Backends, Incident};
13use planarian_txn::store::Store;
14use std::time::Instant;
15
16const LOG_LINES: usize = 1000;
18
19#[derive(Clone, Debug)]
20pub struct Snapshot {
21 console: ConsoleReport,
22 incidents: Vec<Incident>,
23 log: Vec<LogLine>,
24}
25
26#[derive(Clone, Debug)]
28pub struct Busy {
29 pub with: String,
30 pub since: Instant,
31}
32
33pub fn read_only(req: &Request) -> bool {
35 matches!(req, Request::Status | Request::Log { .. } | Request::Incidents | Request::Console)
36}
37
38pub fn describe(req: &Request) -> String {
40 match req {
41 Request::Apply { change, .. } => format!("the change {}", change.0),
42 Request::Ask { phrase, .. } => format!("\"{phrase}\""),
43 Request::Approve { .. } => "an approved change".into(),
44 Request::Undo => "undo".into(),
45 Request::Gc => "garbage collection".into(),
46 Request::Restart { unit } => format!("restarting {unit}"),
47 Request::Stop { unit } => format!("stopping {unit}"),
48 Request::MarkGood { .. } => "mark-good".into(),
49 Request::Resolve { txn, .. } => format!("resolving transaction {}", txn.0),
50 Request::Diagnose { unit, .. } => format!("diagnosing {unit}"),
51 _ => "a request".into(),
52 }
53}
54
55impl Snapshot {
56 pub fn take<B: Backends + Clone, S: Store>(core: &mut Core<B, S>) -> Option<Snapshot> {
58 let me = Peer::new(0, false);
59 let Response::Console(console) = core.handle(me.clone(), Request::Console) else { return None };
60 let Response::Incidents { incidents } = core.handle(me.clone(), Request::Incidents) else { return None };
61 let Response::Log { lines } = core.handle(me, Request::Log { limit: LOG_LINES }) else { return None };
62 Some(Snapshot { console: *console, incidents, log: lines })
63 }
64
65 pub fn answer(&self, req: &Request, busy: &Busy) -> Option<Response> {
66 let mins = busy.since.elapsed().as_secs() / 60;
67 let note = format!(
68 "busy with {} for {}: this is how things stood when it started",
69 busy.with,
70 if mins == 0 { "under a minute".to_string() } else { format!("{mins} min") }
71 );
72 Some(match req {
73 Request::Status => {
74 let mut s = self.console.status.clone();
75 s.notices.insert(0, note);
76 Response::Status(Box::new(s))
77 }
78 Request::Console => {
79 let mut c = self.console.clone();
80 c.status.notices.insert(0, note);
81 Response::Console(Box::new(c))
82 }
83 Request::Incidents => Response::Incidents { incidents: self.incidents.clone() },
84 Request::Log { limit } => {
85 let skip = self.log.len().saturating_sub(*limit);
86 Response::Log { lines: self.log[skip..].to_vec() }
87 }
88 _ => return None,
89 })
90 }
91}