use super::{BlobFileRecord, Section};
use crate::fs::{Fs, FsOpenOptions};
use crate::path::{Path, PathBuf};
use crate::table::DataBlock;
use crate::table::block::{BlockIdentity, BlockTransform, BlockType, ParsedItem};
use crate::vlog::blob_file::meta::{METADATA_HEADER_MAGIC, Metadata};
use crate::vlog::blob_file::scanner::Scanner;
use crate::{Checksum, SeqNo, UserKey, UserValue, vlog::BlobFileId};
use alloc::sync::Arc;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlobFrame {
pub offset: u64,
pub frame_end: u64,
pub key: UserKey,
pub seqno: SeqNo,
pub stored: UserValue,
pub uncompressed_len: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlobFileTotals {
pub item_count: u64,
pub compressed_bytes: u64,
pub uncompressed_bytes: u64,
pub first_key: UserKey,
pub last_key: UserKey,
}
pub struct BlobFileExport {
path: PathBuf,
id: BlobFileId,
live_from: u64,
fs: Arc<dyn Fs>,
meta: Vec<(UserKey, UserValue)>,
sections: Vec<Section>,
created_at: u128,
compression: crate::CompressionType,
totals: BlobFileTotals,
}
impl BlobFileExport {
pub fn open(
path: &Path,
record: &BlobFileRecord,
live_from: u64,
fs: Arc<dyn Fs>,
) -> crate::Result<Self> {
let digest = crate::repair::compute_table_checksum_from(&*fs, path, live_from)?;
if digest != record.checksum {
return Err(crate::Error::ChecksumMismatch {
got: Checksum::from_raw(digest),
expected: Checksum::from_raw(record.checksum),
});
}
let mut file = fs.open(path, &FsOpenOptions::new().read(true))?;
let trailer = crate::sfa::Reader::from_reader(&mut file)?;
let sections: Vec<Section> = trailer
.toc()
.iter()
.map(|entry| Section {
name: entry.name().to_vec(),
offset: entry.pos(),
len: entry.len(),
})
.collect();
let meta_section = trailer
.toc()
.section(b"meta")
.ok_or(crate::Error::InvalidHeader("BlobFileMeta"))?;
let meta_len = usize::try_from(meta_section.len())
.map_err(|_| crate::Error::InvalidHeader("BlobFileMeta"))?;
let meta_bytes = crate::file::read_exact(&*file, meta_section.pos(), meta_len)?;
let parsed = Metadata::from_slice(&meta_bytes)?;
if parsed.id != record.id {
return Err(crate::Error::InvalidHeader("BlobFileMeta"));
}
let mut reader = meta_bytes
.get(METADATA_HEADER_MAGIC.len()..)
.ok_or(crate::Error::InvalidHeader("BlobFileMeta"))?;
let block = DataBlock::new(crate::table::Block::from_reader(
&mut reader,
BlockIdentity {
table_id: 0,
block_type: BlockType::Meta,
dict_id: 0,
window_log: 0,
},
&BlockTransform::PLAIN,
)?);
let meta = block
.try_iter(crate::comparator::default_comparator())?
.map(|item| {
let item = item.materialize(block.as_slice());
(item.key.user_key, item.value)
})
.collect();
Ok(Self {
path: path.to_path_buf(),
id: record.id,
live_from,
fs,
meta,
sections,
created_at: parsed.created_at,
compression: parsed.compression,
totals: BlobFileTotals {
item_count: parsed.item_count,
compressed_bytes: parsed.total_compressed_bytes,
uncompressed_bytes: parsed.total_uncompressed_bytes,
first_key: parsed.key_range.min().clone(),
last_key: parsed.key_range.max().clone(),
},
})
}
#[must_use]
pub fn totals(&self) -> &BlobFileTotals {
&self.totals
}
#[must_use]
pub fn created_at(&self) -> u128 {
self.created_at
}
#[must_use]
pub fn compression(&self) -> crate::CompressionType {
self.compression
}
#[must_use]
pub fn id(&self) -> BlobFileId {
self.id
}
#[must_use]
pub fn sections(&self) -> &[Section] {
&self.sections
}
#[must_use]
pub fn meta(&self) -> &[(UserKey, UserValue)] {
&self.meta
}
pub fn frames(&self) -> crate::Result<Vec<BlobFrame>> {
let scanner = if self.live_from == 0 {
Scanner::new(&self.path, &*self.fs, self.id)?
} else {
Scanner::resume(&self.path, &*self.fs, self.id, self.live_from)?
};
scanner
.map(|entry| {
let entry = entry?;
if entry.resynced {
return Err(crate::Error::InvalidHeader("Blob"));
}
Ok(BlobFrame {
offset: entry.offset,
frame_end: entry.frame_end,
key: entry.key,
seqno: entry.seqno,
stored: entry.value,
uncompressed_len: entry.uncompressed_len,
})
})
.collect()
}
}