use crate::content::node::{PropertyState, PropertyValues};
use crate::content::property::PropertyValue;
use crate::content::provider::SegmentProvider;
use crate::content::value::BinaryValue;
use crate::segment::identifier::SegmentIdentifier;
use crate::tooling::VerifiedContentObserver;
use crate::writer::maintenance::plan::ExternalBinaryFootprint;
use crate::writer::maintenance::planning::version_storage::{
VersionStorageCensus, collect_bulk_blocks, parse_identifier,
};
use std::collections::HashSet;
use std::hash::{DefaultHasher, Hash, Hasher};
#[derive(Default)]
pub(crate) struct PlanningContentCensus {
pub(crate) external_binaries: ExternalBinaryCensus,
pub(crate) version_storage: VersionStorageCensus,
pub(crate) match_live_identifiers: bool,
pub(crate) live_bulk: HashSet<SegmentIdentifier>,
}
impl VerifiedContentObserver for PlanningContentCensus {
fn node_verified(
&mut self,
provider: &dyn SegmentProvider,
_path: &str,
_node: &crate::content::node::NodeState<'_>,
properties: &[PropertyState],
) {
for property in properties {
for value in property_values(property) {
self.external_binaries.observe_value(value);
if let PropertyValue::Binary(crate::content::value::BinaryValue::Inline {
length,
record_identifier,
}) = value
{
collect_bulk_blocks(provider, *record_identifier, *length, &mut self.live_bulk);
}
}
if self.match_live_identifiers
&& property.name == "jcr:uuid"
&& let PropertyValues::Single(PropertyValue::String(text)) = &property.values
&& let Some(identifier) = parse_identifier(text)
{
self.version_storage.mark_live(identifier);
}
}
}
}
fn property_values(property: &PropertyState) -> impl Iterator<Item = &PropertyValue> {
match &property.values {
PropertyValues::Single(value) => std::slice::from_ref(value).iter(),
PropertyValues::Multiple(values) => values.iter(),
}
}
#[derive(Default)]
pub(crate) struct ExternalBinaryCensus {
distinct: HashSet<[u8; 16]>,
measured_bytes: u64,
unmeasured_references: u64,
}
impl ExternalBinaryCensus {
pub(crate) fn observe_value(&mut self, value: &PropertyValue) {
let PropertyValue::Binary(BinaryValue::External { blob_identifier }) = value else {
return;
};
if !self.distinct.insert(distinctness_key(blob_identifier)) {
return;
}
match parse_length_suffix(blob_identifier) {
Some(length) => self.measured_bytes = self.measured_bytes.saturating_add(length),
None => self.unmeasured_references += 1,
}
}
pub(crate) fn footprint(&self) -> ExternalBinaryFootprint {
ExternalBinaryFootprint {
distinct_references: crate::progress::count(self.distinct.len()),
measured_bytes: self.measured_bytes,
unmeasured_references: self.unmeasured_references,
}
}
}
fn distinctness_key(blob_identifier: &str) -> [u8; 16] {
let bytes = blob_identifier.as_bytes();
if bytes.len() >= 32
&& let Some(hexadecimal) = blob_identifier.get(..32)
&& bytes[..32].iter().all(u8::is_ascii_hexdigit)
{
let mut key = [0u8; 16];
for (position, slot) in key.iter_mut().enumerate() {
let pair = &hexadecimal[position * 2..position * 2 + 2];
*slot = u8::from_str_radix(pair, 16).expect("two hexadecimal digits");
}
return key;
}
let mut first_half = DefaultHasher::new();
blob_identifier.hash(&mut first_half);
let mut second_half = DefaultHasher::new();
blob_identifier.len().hash(&mut second_half);
blob_identifier.hash(&mut second_half);
let mut key = [0u8; 16];
key[..8].copy_from_slice(&first_half.finish().to_be_bytes());
key[8..].copy_from_slice(&second_half.finish().to_be_bytes());
key
}
fn parse_length_suffix(blob_identifier: &str) -> Option<u64> {
let (_, suffix) = blob_identifier.rsplit_once('#')?;
suffix.parse().ok()
}
#[cfg(test)]
mod tests {
use super::{ExternalBinaryCensus, distinctness_key, parse_length_suffix};
use crate::content::property::PropertyValue;
use crate::content::value::BinaryValue;
fn external(identifier: &str) -> PropertyValue {
PropertyValue::Binary(BinaryValue::External {
blob_identifier: identifier.to_owned(),
})
}
#[test]
fn measured_references_deduplicate_and_sum_their_lengths() {
let mut census = ExternalBinaryCensus::default();
let first = "00b6d84c92565b98a45f1bb0a9fef2eff804239ba1b96e4ae4e29e0e4222829a#12345";
let second = "ffb6d84c92565b98a45f1bb0a9fef2eff804239ba1b96e4ae4e29e0e4222829a#100";
census.observe_value(&external(first));
census.observe_value(&external(first));
census.observe_value(&external(second));
let footprint = census.footprint();
assert_eq!(footprint.distinct_references, 2);
assert_eq!(footprint.measured_bytes, 12_445);
assert_eq!(footprint.unmeasured_references, 0);
}
#[test]
fn an_unmeasured_reference_is_counted_but_contributes_no_bytes() {
let mut census = ExternalBinaryCensus::default();
census.observe_value(&external("custom-blob-store-identifier"));
census.observe_value(&external("custom-blob-store-identifier"));
let footprint = census.footprint();
assert_eq!(footprint.distinct_references, 1);
assert_eq!(footprint.measured_bytes, 0);
assert_eq!(footprint.unmeasured_references, 1);
}
#[test]
fn non_binary_values_are_ignored() {
let mut census = ExternalBinaryCensus::default();
census.observe_value(&PropertyValue::String("not a binary".to_owned()));
assert_eq!(census.footprint().distinct_references, 0);
}
#[test]
fn distinctness_keys_separate_differing_identifiers() {
let hexadecimal_one = "00b6d84c92565b98a45f1bb0a9fef2ef#1";
let hexadecimal_two = "10b6d84c92565b98a45f1bb0a9fef2ef#1";
assert_ne!(
distinctness_key(hexadecimal_one),
distinctness_key(hexadecimal_two)
);
assert_eq!(
distinctness_key(hexadecimal_one),
distinctness_key(hexadecimal_one)
);
assert_ne!(
distinctness_key("opaque-one"),
distinctness_key("opaque-two")
);
assert_eq!(
distinctness_key("opaque-one"),
distinctness_key("opaque-one")
);
}
#[test]
fn length_parses_from_the_final_hash_separator_only() {
assert_eq!(parse_length_suffix("abc#123"), Some(123));
assert_eq!(parse_length_suffix("a#b#42"), Some(42));
assert_eq!(parse_length_suffix("abc"), None);
assert_eq!(parse_length_suffix("abc#notanumber"), None);
}
}