use std::io::{Read, Write};
use crate::checksum::crc32;
use crate::error::{Error, Result};
use crate::index::lucene::codec::compound::{CompoundFileWriter, write_entry_table};
use crate::index::lucene::codec::data_output::CodecOutput;
use crate::index::lucene::codec::field_infos::{FieldInfo, write_field_infos};
const SEGMENT_INFO_CODEC: &str = "Lucene46SegmentInfo";
const SEGMENT_INFO_VERSION: i32 = 0;
pub const LUCENE_VERSION: &str = "4.7";
const COMPOUND_YES: u8 = 1;
const COMPOUND_NO: u8 = 0xff;
const SEGMENTS: &str = "segments";
const COMMIT_VERSION: i32 = 1;
const FORMAT_SEGMENTS_GEN: i32 = -2;
pub const OAK_CODEC: &str = "oakCodec";
#[derive(Clone, Debug)]
pub struct SegmentDescriptor {
pub document_count: i32,
pub compound: bool,
pub diagnostics: Vec<(String, String)>,
pub files: Vec<String>,
}
#[derive(Clone, Debug)]
pub struct CommittedSegment {
pub name: String,
pub codec_name: String,
}
pub fn write_segment_info<Sink: Write>(sink: Sink, descriptor: &SegmentDescriptor) -> Result<Sink> {
let mut output = CodecOutput::new(sink);
output.write_header(SEGMENT_INFO_CODEC, SEGMENT_INFO_VERSION)?;
output.write_string(LUCENE_VERSION)?;
output.write_int(descriptor.document_count)?;
output.write_byte(if descriptor.compound {
COMPOUND_YES
} else {
COMPOUND_NO
})?;
output.write_string_map(
descriptor
.diagnostics
.iter()
.map(|(key, value)| (key.as_str(), value.as_str())),
)?;
output.write_string_set(descriptor.files.iter().map(String::as_str))?;
Ok(output.into_inner())
}
pub fn write_commit<Sink: Write>(
sink: Sink,
version: i64,
counter: i32,
segments: &[CommittedSegment],
user_data: &[(String, String)],
) -> Result<Sink> {
let mut body = Vec::new();
{
let mut output = CodecOutput::new(&mut body);
output.write_header(SEGMENTS, COMMIT_VERSION)?;
output.write_long(version)?;
output.write_int(counter)?;
output.write_int(segments.len() as i32)?;
for segment in segments {
output.write_string(&segment.name)?;
output.write_string(&segment.codec_name)?;
output.write_long(-1)?;
output.write_int(0)?;
output.write_long(-1)?;
output.write_int(0)?;
}
output.write_string_map(
user_data
.iter()
.map(|(key, value)| (key.as_str(), value.as_str())),
)?;
}
let checksum = i64::from(crc32(&body));
let mut output = CodecOutput::new(sink);
output.write_bytes(&body)?;
output.write_long(checksum)?;
Ok(output.into_inner())
}
pub fn write_generation_file<Sink: Write>(sink: Sink, generation: i64) -> Result<Sink> {
let mut output = CodecOutput::new(sink);
output.write_int(FORMAT_SEGMENTS_GEN)?;
output.write_long(generation)?;
output.write_long(generation)?;
Ok(output.into_inner())
}
pub trait SegmentDirectory {
fn write_file(
&mut self,
name: &str,
write: &mut dyn FnMut(&mut dyn Write) -> Result<()>,
) -> Result<()>;
}
pub struct SegmentOutputs<'source> {
pub document_count: i32,
pub files: Vec<(String, Box<dyn Read + 'source>)>,
}
pub fn assemble_segment(
directory: &mut impl SegmentDirectory,
segment: &str,
fields: &[FieldInfo],
outputs: SegmentOutputs<'_>,
) -> Result<SegmentDescriptor> {
let field_infos = write_field_infos(Vec::new(), fields)?;
let mut sources = outputs.files;
let mut entries = Vec::new();
let mut failure = None;
directory.write_file(&format!("{segment}.cfs"), &mut |sink| {
let mut writer = CompoundFileWriter::new(sink)?;
writer.add(&format!("{segment}.fnm"), &mut field_infos.as_slice())?;
for (name, source) in &mut sources {
writer.add(name, source)?;
}
let (_, written) = writer.finish();
entries = written;
Ok(())
})?;
directory.write_file(&format!("{segment}.cfe"), &mut |sink| {
if let Err(error) = write_entry_table(sink, &entries) {
failure = Some(error);
}
Ok(())
})?;
if let Some(error) = failure {
return Err(error);
}
let descriptor = SegmentDescriptor {
document_count: outputs.document_count,
compound: true,
diagnostics: vec![
("source".to_owned(), "froe".to_owned()),
("lucene.version".to_owned(), LUCENE_VERSION.to_owned()),
],
files: vec![
format!("{segment}.cfs"),
format!("{segment}.cfe"),
format!("{segment}.si"),
],
};
let mut failure = None;
directory.write_file(&format!("{segment}.si"), &mut |sink| {
if let Err(error) = write_segment_info(sink, &descriptor) {
failure = Some(error);
}
Ok(())
})?;
if let Some(error) = failure {
return Err(error);
}
Ok(descriptor)
}
pub fn write_commit_files(
directory: &mut impl SegmentDirectory,
segments: &[CommittedSegment],
) -> Result<()> {
let counter = i32::try_from(segments.len()).map_err(|_| Error::InvalidFormat {
details: format!("{} segments does not fit the counter", segments.len()),
})?;
let mut failure = None;
directory.write_file("segments_1", &mut |sink| {
if let Err(error) = write_commit(sink, 0, counter, segments, &[]) {
failure = Some(error);
}
Ok(())
})?;
if let Some(error) = failure {
return Err(error);
}
directory.write_file("segments.gen", &mut |sink| {
if let Err(error) = write_generation_file(sink, 1) {
failure = Some(error);
}
Ok(())
})?;
if let Some(error) = failure {
return Err(error);
}
Ok(())
}