use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use std::sync::Arc;
use pedant_types::{SourcePath, SourceRecord};
use crate::hash::digest_bytes;
use crate::observe::{self, Observation};
use super::capacity::admits_one_more;
use super::read;
use super::source_language::SourceLanguage;
#[derive(Debug)]
pub(crate) struct RecordCache<Inventory> {
root: PathBuf,
records: BTreeMap<Arc<str>, SourceRecord<Inventory>>,
refused: BTreeSet<Arc<str>>,
consumed: u64,
}
impl<Inventory> RecordCache<Inventory> {
pub(crate) fn new(root: PathBuf) -> Self {
Self {
root,
records: BTreeMap::new(),
refused: BTreeSet::new(),
consumed: 0,
}
}
pub(crate) fn retained(&self, path: &str) -> Option<&SourceRecord<Inventory>> {
self.records.get(path)
}
pub(crate) fn admitted_paths(&self) -> Box<[Arc<str>]> {
self.records.keys().map(Arc::clone).collect()
}
pub(crate) fn admitted_sources(&self) -> usize {
self.records.len()
}
pub(crate) fn retained_bytes(&self) -> u64 {
self.consumed
}
pub(crate) fn source<L>(
&mut self,
language: &L,
path: SourcePath<'_>,
) -> Result<SourceRecord<Inventory>, L::Fault>
where
L: SourceLanguage<Inventory = Inventory>,
{
match self.records.get(path.as_str()) {
Some(record) => Ok(record.clone()),
None => self.unheld(language, path),
}
}
fn unheld<L>(
&mut self,
language: &L,
path: SourcePath<'_>,
) -> Result<SourceRecord<Inventory>, L::Fault>
where
L: SourceLanguage<Inventory = Inventory>,
{
match self.refused.contains(path.as_str()) {
true => Err(language.refused_again(path.as_str())),
false => self.admit(language, path),
}
}
fn admit<L>(
&mut self,
language: &L,
path: SourcePath<'_>,
) -> Result<SourceRecord<Inventory>, L::Fault>
where
L: SourceLanguage<Inventory = Inventory>,
{
self.check_capacity(language)?;
match self.attempt(language, path) {
Ok(record) => Ok(record),
Err(fault) => {
self.refused.insert(Arc::from(path.as_str()));
Err(fault)
}
}
}
fn attempt<L>(
&mut self,
language: &L,
path: SourcePath<'_>,
) -> Result<SourceRecord<Inventory>, L::Fault>
where
L: SourceLanguage<Inventory = Inventory>,
{
let relative = path.as_str();
let canonical = language.confine(&self.root, relative)?;
observe::record(Observation::SourceRead(relative));
let bytes = read::bounded(&canonical, language.bounds(self.consumed))
.map_err(|fault| language.refusal(fault))?;
let charged = read::byte_count(&bytes);
let text = language.decode(bytes)?;
let inventory = language.inventory(relative, &text)?;
let record = SourceRecord::new(
Arc::from(text.as_str()),
digest_bytes(text.as_bytes()),
Arc::new(inventory),
);
self.consumed = self.consumed.saturating_add(charged);
self.records.insert(Arc::from(relative), record.clone());
Ok(record)
}
fn check_capacity<L>(&self, language: &L) -> Result<(), L::Fault>
where
L: SourceLanguage<Inventory = Inventory>,
{
let retained = self.records.len().saturating_add(self.refused.len());
match admits_one_more(retained, language.max_source_files()) {
true => Ok(()),
false => Err(language.source_files_fault()),
}
}
}