use std::{collections::HashSet, io};
use ltk_hash::BinHash;
use crate::{
property::NoMeta,
stream::{BinStream, ObjectEntry, ObjectStream},
Error,
};
#[must_use = "cursors are lazy and read nothing until advanced"]
#[derive(Debug)]
pub struct BatchObjects<'a, R: io::Read + io::Seek, M = NoMeta> {
stream: &'a mut BinStream<R, M>,
requested: Vec<BinHash>,
pending: HashSet<BinHash>,
missing: Vec<BinHash>,
ordered: Option<Vec<ObjectEntry>>,
at: usize,
}
impl<'a, R: io::Read + io::Seek, M: Default> BatchObjects<'a, R, M> {
pub(crate) fn new(
stream: &'a mut BinStream<R, M>,
hashes: impl IntoIterator<Item = impl Into<BinHash>>,
) -> Self {
let mut requested = Vec::new();
let mut pending = HashSet::new();
for hash in hashes {
let hash = hash.into();
if pending.insert(hash) {
requested.push(hash);
}
}
let (ordered, missing) = match stream.is_toc_complete() {
false => (None, Vec::new()),
true => {
let mut found = Vec::with_capacity(requested.len());
let mut missing = Vec::new();
for &hash in &requested {
match stream.toc_entry(hash) {
Some(&entry) => found.push(entry),
None => missing.push(hash),
}
}
found.sort_unstable_by_key(|entry| entry.offset);
pending.clear();
(Some(found), missing)
}
};
Self {
stream,
requested,
pending,
missing,
ordered,
at: 0,
}
}
#[expect(
clippy::should_implement_trait,
reason = "a lending cursor: the yielded item borrows the reader, which `Iterator` cannot express"
)]
pub fn next(&mut self) -> Result<Option<ObjectStream<'_, R, M>>, Error> {
match self.next_entry()? {
Some(entry) => Ok(Some(ObjectStream::new(self.stream, entry))),
None => Ok(None),
}
}
#[must_use]
pub fn missing(&self) -> &[BinHash] {
&self.missing
}
fn next_entry(&mut self) -> Result<Option<ObjectEntry>, Error> {
if let Some(ordered) = &self.ordered {
let entry = ordered.get(self.at).copied();
if entry.is_some() {
self.at += 1;
}
return Ok(entry);
}
let total = self.stream.class_hashes().len();
if self.pending.is_empty() {
self.at = total;
}
while self.at < total {
let entry = match self.stream.toc_row(self.at) {
Some(&entry) => entry,
None => match self.stream.harvest(self.at) {
Ok(entry) => entry,
Err(error) => {
self.at = total;
return Err(error);
}
},
};
self.at += 1;
if self.pending.remove(&entry.path_hash) {
if self.pending.is_empty() {
self.at = total;
}
return Ok(Some(entry));
}
}
let pending = &self.pending;
let missing: Vec<_> = self
.requested
.iter()
.copied()
.filter(|hash| pending.contains(hash))
.collect();
self.missing = missing;
Ok(None)
}
}