fwob-v1 1.5.2

Reader, writer, verifier, and editor for the FWOB v1 binary format
Documentation
use std::{fs::File, ops::Range, path::Path};

use fwob_core::{
    FileInfo, FormatVersion, Key, Maintenance, OwnedFrame, Reader as CoreReader, ReaderBackend,
    ReaderOptions, Result as CoreResult, Schema, VerificationReport, Writer as CoreWriter,
    WriterBackend, WriterFactory,
};

use crate::{Reader, Writer, WriterOptions};

pub struct ReaderAdapter {
    reader: Reader<File>,
    string_table: Vec<String>,
}

impl ReaderAdapter {
    pub fn open(
        path: impl AsRef<Path>,
        key_field_index: usize,
    ) -> crate::Result<(Self, CompatibleWriterFactory)> {
        let mut reader = Reader::open(path, key_field_index)?;
        let string_table = reader.read_string_table()?;
        let factory = CompatibleWriterFactory {
            schema: reader.schema().clone(),
            string_table_preserved_length: reader.header().string_table_preserved_length,
        };
        Ok((
            Self {
                reader,
                string_table,
            },
            factory,
        ))
    }
}

impl FileInfo for ReaderAdapter {
    fn format_version(&self) -> FormatVersion {
        FormatVersion::V1
    }

    fn schema(&self) -> &Schema {
        self.reader.schema()
    }

    fn title(&self) -> &str {
        &self.reader.header().title
    }

    fn frame_count(&self) -> u64 {
        self.reader.frame_count()
    }

    fn string_table(&self) -> &[String] {
        &self.string_table
    }
}

impl ReaderBackend for ReaderAdapter {
    fn read_frame(&mut self, index: u64) -> CoreResult<Option<OwnedFrame>> {
        self.reader
            .read_frame_at(index)
            .map_err(fwob_core::FwobError::backend)
    }

    fn read_key(&mut self, index: u64) -> CoreResult<Option<Key>> {
        self.reader
            .read_key_at(index)
            .map_err(fwob_core::FwobError::backend)
    }

    fn lower_bound(&mut self, key: Key) -> CoreResult<u64> {
        self.reader
            .lower_bound(key)
            .map_err(fwob_core::FwobError::backend)
    }

    fn upper_bound(&mut self, key: Key) -> CoreResult<u64> {
        self.reader
            .upper_bound(key)
            .map_err(fwob_core::FwobError::backend)
    }

    fn equal_range(&mut self, key: Key) -> CoreResult<Range<u64>> {
        let (start, end) = self
            .reader
            .equal_range(key)
            .map_err(fwob_core::FwobError::backend)?;
        Ok(start..end)
    }
}

pub struct CompatibleWriterFactory {
    schema: Schema,
    string_table_preserved_length: u32,
}

impl WriterFactory for CompatibleWriterFactory {
    fn create(
        &mut self,
        path: &Path,
        title: &str,
        string_table: &[String],
    ) -> CoreResult<CoreWriter> {
        let required = string_table
            .iter()
            .map(|value| value.len().saturating_add(5))
            .sum::<usize>();
        let mut options = WriterOptions::new(title);
        options.string_table_preserved_length = self
            .string_table_preserved_length
            .max(u32::try_from(required).unwrap_or(u32::MAX));
        create_writer(path, self.schema.clone(), options, string_table)
            .map_err(fwob_core::FwobError::backend)
    }
}

pub struct WriterAdapter {
    writer: Writer<File>,
    string_table: Vec<String>,
}

impl WriterAdapter {
    pub fn create(
        path: impl AsRef<Path>,
        schema: Schema,
        options: WriterOptions,
        string_table: &[String],
    ) -> crate::Result<Self> {
        let mut writer = Writer::create(path, schema, options)?;
        for value in string_table {
            writer.append_string(value)?;
        }
        Ok(Self {
            writer,
            string_table: string_table.to_vec(),
        })
    }

    pub fn open_append(path: impl AsRef<Path>, key_field_index: usize) -> crate::Result<Self> {
        let mut reader = Reader::open(path.as_ref(), key_field_index)?;
        let string_table = reader.read_string_table()?;
        drop(reader);
        Ok(Self {
            writer: Writer::open_append(path, key_field_index)?,
            string_table,
        })
    }
}

impl FileInfo for WriterAdapter {
    fn format_version(&self) -> FormatVersion {
        FormatVersion::V1
    }

    fn schema(&self) -> &Schema {
        self.writer.schema()
    }

    fn title(&self) -> &str {
        &self.writer.header().title
    }

    fn frame_count(&self) -> u64 {
        self.writer.frame_count()
    }

    fn string_table(&self) -> &[String] {
        &self.string_table
    }
}

impl WriterBackend for WriterAdapter {
    fn append_frame(&mut self, frame: &[u8]) -> CoreResult<()> {
        self.writer
            .append_frame(frame)
            .map_err(fwob_core::FwobError::backend)
    }

    fn append_presorted_frames(&mut self, frames: &[u8]) -> CoreResult<()> {
        self.writer
            .append_presorted_raw_frames(frames)
            .map_err(fwob_core::FwobError::backend)
    }

    fn append_frames_transactional(&mut self, frames: &[u8]) -> CoreResult<()> {
        self.writer
            .append_raw_frames_transactional(frames)
            .map_err(fwob_core::FwobError::backend)
    }

    fn finish(self: Box<Self>) -> CoreResult<()> {
        Ok(())
    }
}

pub fn open_reader(path: impl AsRef<Path>, key_field_index: usize) -> crate::Result<CoreReader> {
    let (reader, factory) = ReaderAdapter::open(path, key_field_index)?;
    Ok(CoreReader::from_parts(reader, factory))
}

pub fn create_writer(
    path: impl AsRef<Path>,
    schema: Schema,
    options: WriterOptions,
    string_table: &[String],
) -> crate::Result<CoreWriter> {
    Ok(CoreWriter::from_backend(WriterAdapter::create(
        path,
        schema,
        options,
        string_table,
    )?))
}

pub fn open_writer(path: impl AsRef<Path>, key_field_index: usize) -> crate::Result<CoreWriter> {
    Ok(CoreWriter::from_backend(WriterAdapter::open_append(
        path,
        key_field_index,
    )?))
}

#[derive(Debug, Default, Clone, Copy)]
pub struct MaintenanceService;

impl Maintenance for MaintenanceService {
    fn format_version(&self) -> FormatVersion {
        FormatVersion::V1
    }

    fn light_verify(&self, path: &Path, options: ReaderOptions) -> CoreResult<VerificationReport> {
        let (reader, _) = ReaderAdapter::open(path, options.v1_key_field_index)
            .map_err(fwob_core::FwobError::backend)?;
        Ok(VerificationReport {
            format_version: FormatVersion::V1,
            frame_count: reader.frame_count(),
            string_count: reader.string_table().len() as u32,
            file_length: std::fs::metadata(path)?.len(),
        })
    }

    fn verify(&self, path: &Path, options: ReaderOptions) -> CoreResult<VerificationReport> {
        self.light_verify(path, options)?;
        let report = crate::verify_file(path, options.v1_key_field_index)
            .map_err(fwob_core::FwobError::backend)?;
        Ok(VerificationReport {
            format_version: FormatVersion::V1,
            frame_count: report.frame_count,
            string_count: report.string_count,
            file_length: report.file_length,
        })
    }

    fn repair(&self, path: &Path, options: ReaderOptions) -> CoreResult<VerificationReport> {
        let report = crate::repair_committed_tail(path, options.v1_key_field_index)
            .map_err(fwob_core::FwobError::backend)?;
        Ok(VerificationReport {
            format_version: FormatVersion::V1,
            frame_count: report.frame_count,
            string_count: report.string_count,
            file_length: report.file_length,
        })
    }
}