artifactize_core/monitor/live/
loader.rs1use super::{Branch, Discovery, Labels, Place, branches};
6use crate::{
7 changes::Changed,
8 monitor::{Node, Origin, model},
9 platform::Footprint,
10 store::{self, CatalogRun, RequestView, RunView, Unreadable, reader::Reader},
11 types::RunId,
12};
13use std::{path::PathBuf, sync::Arc};
14use time::OffsetDateTime;
15
16#[cfg(test)]
17mod tests;
18
19#[derive(Debug, Clone, Default)]
21pub struct Input {
22 pub state: PathBuf,
23 pub launch: Option<PathBuf>,
25 pub open: Option<RunId>,
27 pub force: bool,
29 pub rescan: bool,
31 pub changed: Changed,
33}
34
35pub type Opened = (RunView, Vec<RequestView>, Arc<Vec<Node>>, Footprint);
38
39pub struct Snapshot {
41 pub origin: Origin,
42 pub branches: Vec<Branch>,
44 pub labels: Labels,
46 pub launch: Option<Place>,
48 pub unreadable: Vec<Unreadable>,
50 pub opened: Option<Opened>,
53 pub open: bool,
55}
56
57#[derive(Default)]
59pub struct Loader {
60 reader: Reader,
61 discovery: Discovery,
62 facts: Option<(Arc<Vec<CatalogRun>>, Vec<Unreadable>)>,
64}
65
66impl Loader {
67 pub fn load(&mut self, input: Input) -> Result<Snapshot, String> {
68 if input.rescan {
69 self.discovery.rescan();
70 }
71 let database = input.state.join(store::DATABASE);
72 if !database.try_exists().map_err(|error| error.to_string())? {
73 store::check_probe_files(&input.state)?;
75 self.reader.reset();
76 self.facts = None;
77 return Ok(self.empty(&input, Origin::default()));
78 }
79 let facts = &mut self.facts;
80 let saved = self
81 .reader
82 .snapshot_optional(&input.state, |db, cache, state| {
83 let changed = input.changed.or_written(cache.changed());
84 match (facts.as_mut(), &changed) {
85 (Some((runs, unreadable)), Changed::Runs(ids)) if cache.has_catalog() => {
86 let ids: Vec<_> = ids.iter().cloned().collect();
87 let rows = cache.catalog_rows(db, state, &ids)?;
88 *runs = Arc::new(merged(runs, &ids, rows.runs));
89 unreadable.retain(|record| !ids.iter().any(|id| record.id == id.as_str()));
90 unreadable.extend(rows.unreadable);
91 }
92 (Some(_), Changed::Nothing) if cache.has_catalog() => {}
93 _ => {
94 let runs = cache.catalog(db, state)?;
95 *facts = Some((Arc::new(runs), cache.unreadable_runs()));
96 }
97 }
98 let (runs, _) = facts.as_ref().expect("read above");
99 let open = input
100 .open
101 .as_ref()
102 .filter(|id| runs.iter().any(|run| &run.id == *id));
103 if open.is_none() {
104 cache.close();
106 }
107 let read = open.filter(|id| input.force || !cache.holds(id) || changed.run(id));
109 let opened = match read {
110 Some(id) => match cache.selected(db, state, id, input.force) {
111 Ok(opened) => Ok(opened.map(|(run, requests)| {
112 let footprint = cache.footprint().expect("the Run just read is held");
113 (run, requests, footprint)
114 })),
115 Err(store::receipts::Error::Unreadable(record)) => Err(record),
117 Err(error) => return Err(error),
118 },
119 None => Ok(None),
120 };
121 Ok((cache.identity(db)?, open.is_some(), opened))
122 })?;
123 let Some((identity, open, opened)) = saved else {
124 self.facts = None;
125 return Ok(self.empty(&input, Origin::new(None, self.reader.database())));
126 };
127 let (opened, unopened) = match opened {
128 Ok(opened) => (opened, None),
129 Err(record) => (None, Some(record)),
130 };
131 let opened = opened.map(|(run, requests, footprint)| {
133 let nodes = Arc::new(model::tree(&run, &requests, OffsetDateTime::now_utc()));
134 (run, requests, nodes, footprint)
135 });
136 let (runs, mut unreadable) = self.facts.clone().expect("read above");
137 if let Some((run, _, _, _)) = &opened {
138 unreadable.extend(run.unreadable.iter().cloned());
139 }
140 unreadable.extend(unopened);
141 let listing = branches(&runs, input.launch.as_deref(), &mut self.discovery);
142 self.reader.require_current(&input.state)?;
143 Ok(Snapshot {
144 origin: Origin::new(identity, self.reader.database()),
145 branches: listing.branches,
146 labels: listing.labels,
147 launch: listing.launch,
148 unreadable,
149 opened,
150 open,
151 })
152 }
153
154 fn empty(&mut self, input: &Input, origin: Origin) -> Snapshot {
155 let listing = branches(&[], input.launch.as_deref(), &mut self.discovery);
156 Snapshot {
157 origin,
158 branches: listing.branches,
159 labels: listing.labels,
160 launch: listing.launch,
161 unreadable: Vec::new(),
162 opened: None,
163 open: false,
164 }
165 }
166}
167
168fn merged(runs: &[CatalogRun], ids: &[RunId], rows: Vec<CatalogRun>) -> Vec<CatalogRun> {
171 let mut rows: std::collections::BTreeMap<_, _> =
172 rows.into_iter().map(|run| (run.id.clone(), run)).collect();
173 let mut merged: Vec<_> = runs
174 .iter()
175 .filter_map(|run| {
176 if ids.contains(&run.id) {
177 rows.remove(&run.id)
178 } else {
179 Some(run.clone())
180 }
181 })
182 .collect();
183 merged.extend(rows.into_values());
184 merged
185}