aspero_engine/
versions.rs1use aspero_core::{FieldSet, ProviderId, SourceRecord, Usage, WorkId};
5
6use crate::Engine;
7
8const MAX_HOPS: usize = 20;
11
12#[derive(Debug, Clone, Default)]
13pub struct VersionsReport {
14 pub latest: Vec<Option<SourceRecord>>,
18 pub usage: Vec<(ProviderId, Usage)>,
19}
20
21impl Engine {
22 pub async fn latest_versions(&self, works: &[&SourceRecord]) -> VersionsReport {
27 let mut latest: Vec<Option<SourceRecord>> = vec![None; works.len()];
28 let mut usage = Vec::new();
29 let Some(entry) = self
30 .entries
31 .iter()
32 .find(|e| e.provider.capabilities().notices)
33 else {
34 return VersionsReport { latest, usage };
35 };
36 let mut frontier: Vec<(usize, WorkId, Vec<WorkId>)> = works
38 .iter()
39 .enumerate()
40 .filter_map(|(i, r)| Some((i, r.work.newer_version()?.clone(), r.ids.clone())))
41 .collect();
42 for _ in 0..MAX_HOPS {
43 if frontier.is_empty() {
44 break;
45 }
46 let ids: Vec<WorkId> = frontier.iter().map(|(_, id, _)| id.clone()).collect();
47 let answers = self.lookup_at(entry, &ids, FieldSet::ALL, &mut usage).await;
48 let mut next = Vec::new();
49 for ((i, id, mut seen), (_, record)) in frontier.into_iter().zip(answers) {
50 let Some(record) = record else { continue };
51 seen.push(id);
52 if let Some(newer) = record.work.newer_version()
53 && !seen.contains(newer)
54 {
55 next.push((i, newer.clone(), seen));
56 }
57 latest[i] = Some(record);
58 }
59 frontier = next;
60 }
61 VersionsReport { latest, usage }
62 }
63}