1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//! Following newer versions to the latest: Cochrane reviews are superseded
//! `.pub6` → `.pub7` → …, each version's record naming only the next.
use aspero_core::{FieldSet, ProviderId, SourceRecord, Usage, WorkId};
use crate::Engine;
/// Most versions followed per work; a loop guard, never reached in
/// practice.
const MAX_HOPS: usize = 20;
#[derive(Debug, Clone, Default)]
pub struct VersionsReport {
/// Per work, in order: the latest version found, if the work has a
/// newer one. When the chain breaks (a version can't be looked up), the
/// last one found.
pub latest: Vec<Option<SourceRecord>>,
pub usage: Vec<(ProviderId, Usage)>,
}
impl Engine {
/// The latest version of each work its record says is superseded
/// ([`Work::newer_version`](aspero_core::Work::newer_version)), asking
/// the providers that report notices. One batched lookup per step along
/// the chains.
pub async fn latest_versions(&self, works: &[&SourceRecord]) -> VersionsReport {
let mut latest: Vec<Option<SourceRecord>> = vec![None; works.len()];
let mut usage = Vec::new();
let Some(entry) = self
.entries
.iter()
.find(|e| e.provider.capabilities().notices)
else {
return VersionsReport { latest, usage };
};
// (work index, next version, versions seen) per chain still going.
let mut frontier: Vec<(usize, WorkId, Vec<WorkId>)> = works
.iter()
.enumerate()
.filter_map(|(i, r)| Some((i, r.work.newer_version()?.clone(), r.ids.clone())))
.collect();
for _ in 0..MAX_HOPS {
if frontier.is_empty() {
break;
}
let ids: Vec<WorkId> = frontier.iter().map(|(_, id, _)| id.clone()).collect();
let answers = self.lookup_at(entry, &ids, FieldSet::ALL, &mut usage).await;
let mut next = Vec::new();
for ((i, id, mut seen), (_, record)) in frontier.into_iter().zip(answers) {
let Some(record) = record else { continue };
seen.push(id);
if let Some(newer) = record.work.newer_version()
&& !seen.contains(newer)
{
next.push((i, newer.clone(), seen));
}
latest[i] = Some(record);
}
frontier = next;
}
VersionsReport { latest, usage }
}
}