use std::collections::BTreeMap;
use std::path::Path;
use std::sync::Arc;
use pedant_types::{SourcePath, SourceRecord};
use super::capacity::admits_one_more;
use super::path_normalization;
use super::read::byte_count;
use super::snapshot_rules::SnapshotRules;
use super::supply::SourceSupply;
pub(crate) enum Interned {
Held(Placement),
Admitted(Placement),
}
pub(crate) struct Placement {
pub(crate) path: Arc<str>,
pub(crate) index: usize,
}
pub(crate) struct SnapshotStoreOf<Rules: SnapshotRules> {
rules: Rules,
indexes: BTreeMap<Arc<str>, usize>,
stored: Vec<Rules::Stored>,
key: String,
consumed: u64,
}
impl<Rules: SnapshotRules> SnapshotStoreOf<Rules> {
pub(crate) fn new(rules: Rules) -> Self {
Self {
rules,
indexes: BTreeMap::new(),
stored: Vec::new(),
key: String::new(),
consumed: 0,
}
}
pub(crate) fn rules(&self) -> &Rules {
&self.rules
}
pub(crate) fn intern<P, Retain>(
&mut self,
provider: &mut P,
canonical: &Path,
at: &Rules::Site,
retain: Retain,
) -> Result<Interned, Rules::Refusal>
where
P: SourceSupply<Rules::Inventory, Rules::Fault>,
Retain: FnOnce(
&Arc<str>,
&SourceRecord<Rules::Inventory>,
) -> Result<Rules::Stored, Rules::Refusal>,
{
self.render_key(canonical, at)?;
let held = self
.indexes
.get_key_value(self.key.as_str())
.map(|(interned, index)| (Arc::clone(interned), *index));
match held {
Some((path, index)) => Ok(Interned::Held(Placement { path, index })),
None => {
let path: Arc<str> = Arc::from(self.key.as_str());
let index = self.admit(provider, &path, at, retain)?;
Ok(Interned::Admitted(Placement { path, index }))
}
}
}
pub(crate) fn stored(&self, index: usize) -> Option<&Rules::Stored> {
self.stored.get(index)
}
pub(crate) fn stored_at(&self, path: &str) -> Option<&Rules::Stored> {
self.indexes
.get(path)
.and_then(|index| self.stored.get(*index))
}
pub(crate) fn keys(&self) -> Box<[Arc<str>]> {
self.indexes.keys().map(Arc::clone).collect()
}
pub(crate) fn finish(self) -> Box<[Rules::Stored]> {
let mut stored = self.stored;
stored.sort_by(|left, right| Rules::stored_path(left).cmp(Rules::stored_path(right)));
stored.into_boxed_slice()
}
fn admit<P, Retain>(
&mut self,
provider: &mut P,
path: &Arc<str>,
at: &Rules::Site,
retain: Retain,
) -> Result<usize, Rules::Refusal>
where
P: SourceSupply<Rules::Inventory, Rules::Fault>,
Retain: FnOnce(
&Arc<str>,
&SourceRecord<Rules::Inventory>,
) -> Result<Rules::Stored, Rules::Refusal>,
{
let relative: &str = path;
self.check_capacity(at, relative)?;
let request =
SourcePath::new(relative).ok_or_else(|| self.rules.unnormalized((at, relative)))?;
let record = provider
.supply(request)
.map_err(|fault| self.rules.refused(fault, (at, relative)))?;
let charged = self.charge(&record, (at, relative))?;
let stored = retain(path, &record)?;
let index = self.stored.len();
self.consumed = self.consumed.saturating_add(charged);
self.indexes.insert(Arc::clone(path), index);
self.stored.push(stored);
Ok(index)
}
fn render_key(&mut self, canonical: &Path, at: &Rules::Site) -> Result<(), Rules::Refusal> {
let rendered =
path_normalization::write_relative(&mut self.key, self.rules.root(), canonical);
rendered.map_err(|error| self.rules.unrelative(error, (at, canonical)))
}
fn charge(
&self,
record: &SourceRecord<Rules::Inventory>,
at: (&Rules::Site, &str),
) -> Result<u64, Rules::Refusal> {
let charged = byte_count(record.text().as_bytes());
match self.rules.retention(self.consumed, charged) {
Some(fault) => Err(self.rules.oversized(fault, at)),
None => Ok(charged),
}
}
fn check_capacity(&self, at: &Rules::Site, path: &str) -> Result<(), Rules::Refusal> {
match admits_one_more(self.stored.len(), self.rules.max_source_files()) {
true => Ok(()),
false => Err(self.rules.source_files((at, path))),
}
}
}