use std::collections::BTreeMap;
use std::io::Read;
use crate::content::property::PropertyType;
use crate::error::{Error, Result};
use crate::index::lucene::directory::{
BLOB_SIZE_PROPERTY, DIRECTORY_LISTING_PROPERTY, UNIQUE_KEY_PROPERTY, UNIQUE_KEY_SIZE,
};
use crate::segment::record::RecordIdentifier;
use crate::writer::record_writer::{
ChildNodesToWrite, PropertyToWrite, PropertyValuesToWrite, RecordWriter, SegmentSink,
};
pub const DEFAULT_BLOB_SIZE: i64 = 1_048_576 - 4096;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DirectoryListing {
Saved,
Omitted,
}
pub struct OakDirectoryWriter<'writer, Sink: SegmentSink> {
writer: &'writer mut RecordWriter<Sink>,
blob_size: i64,
listing: DirectoryListing,
files: BTreeMap<String, RecordIdentifier>,
}
impl<'writer, Sink: SegmentSink> OakDirectoryWriter<'writer, Sink> {
pub fn new(
writer: &'writer mut RecordWriter<Sink>,
blob_size: i64,
listing: DirectoryListing,
) -> Self {
Self {
writer,
blob_size,
listing,
files: BTreeMap::new(),
}
}
pub fn add_file(&mut self, name: &str, reader: impl Read) -> Result<()> {
if self.files.contains_key(name) {
return Err(Error::InvalidFormat {
details: format!(
"the index directory already holds a file named {name}; a directory \
with two files of one name is not one Oak can read"
),
});
}
let mut key = [0u8; UNIQUE_KEY_SIZE as usize];
crate::writer::identifier_generator::random_bytes(&mut key)?;
let mut key_text = String::with_capacity(key.len() * 2);
for byte in &key {
use std::fmt::Write as _;
let _ = write!(key_text, "{byte:02x}");
}
let value = self.writer.write_binary_stream(reader.chain(&key[..]))?;
let properties = vec![
PropertyToWrite {
name: UNIQUE_KEY_PROPERTY.to_owned(),
property_type: PropertyType::String,
values: PropertyValuesToWrite::Single(self.writer.write_string(&key_text)?),
},
PropertyToWrite {
name: BLOB_SIZE_PROPERTY.to_owned(),
property_type: PropertyType::Long,
values: PropertyValuesToWrite::Single(
self.writer.write_string(&self.blob_size.to_string())?,
),
},
PropertyToWrite {
name: "jcr:lastModified".to_owned(),
property_type: PropertyType::Long,
values: PropertyValuesToWrite::Single(
self.writer
.write_string(&epoch_milliseconds().to_string())?,
),
},
PropertyToWrite {
name: "jcr:data".to_owned(),
property_type: PropertyType::Binary,
values: PropertyValuesToWrite::Single(value),
},
];
let node = self
.writer
.write_node(None, &[], &ChildNodesToWrite::Zero, &properties)?;
self.files.insert(name.to_owned(), node);
Ok(())
}
pub fn finish(self) -> Result<RecordIdentifier> {
let children: Vec<(String, RecordIdentifier)> = self
.files
.iter()
.map(|(name, record)| (name.clone(), *record))
.collect();
let mut properties = Vec::new();
if self.listing == DirectoryListing::Saved {
let names: Vec<RecordIdentifier> = self
.files
.keys()
.map(|name| self.writer.write_string(name))
.collect::<Result<_>>()?;
properties.push(PropertyToWrite {
name: DIRECTORY_LISTING_PROPERTY.to_owned(),
property_type: PropertyType::String,
values: PropertyValuesToWrite::Multiple(names),
});
}
self.writer.write_node(
None,
&[],
&match children.as_slice() {
[] => ChildNodesToWrite::Zero,
[(name, node)] => ChildNodesToWrite::One {
name: name.clone(),
node: *node,
},
many => ChildNodesToWrite::Many(many.to_vec()),
},
&properties,
)
}
}
fn epoch_milliseconds() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |elapsed| i64::try_from(elapsed.as_millis()).unwrap_or(0))
}