use std::{
fmt::{self, Debug, Formatter},
path::Path,
sync::Arc,
};
use eyre::Result;
use tantivy::{HasLen, directory::FileSlice};
use tantivy_common::{DeserializeFrom, OwnedBytes};
use tokio::sync::OnceCell;
#[derive(Clone)]
pub struct Footer {
data: Arc<OnceCell<OwnedBytes>>,
pub offset: usize,
}
fn has_composite_footer(path: &Path) -> bool {
matches!(
path.extension().and_then(|ext| ext.to_str()),
Some("idx" | "pos" | "term" | "fieldnorm")
)
}
fn has_columnar_footer(path: &Path) -> bool {
path.extension().and_then(|ext| ext.to_str()) == Some("fast")
}
fn has_store_footer(path: &Path) -> bool {
path.extension().and_then(|ext| ext.to_str()) == Some("store")
}
impl Footer {
pub fn with_offset(offset: usize) -> Self {
Self {
data: Arc::default(),
offset,
}
}
pub async fn read(path: &Path, file: &FileSlice) -> Result<Self> {
let file_len = file.len();
let md_meta = file
.read_bytes_slice_async((file_len - 8)..file_len)
.await?;
let (md_len, _): (u32, u32) = md_meta.as_ref().deserialize()?;
let md_start = file_len - 8 - md_len as usize;
let offset = if has_composite_footer(path) {
let cf_meta = file
.read_bytes_slice_async((md_start - 4)..md_start)
.await?;
let cf_len: u32 = cf_meta.as_ref().deserialize()?;
md_start - 4 - cf_len as usize
} else if has_columnar_footer(path) {
let footers = file
.read_bytes_slice_async((md_start - 40)..md_start)
.await?;
let mut buf = &footers[20..28];
let sstable_len: u64 = buf.deserialize()?;
let mut buf = &footers[0..8];
let index_offset: u64 = buf.deserialize()?;
let sstable_start = md_start - 20 - sstable_len as usize;
sstable_start + index_offset as usize
} else if has_store_footer(path) {
let store_footer = file
.read_bytes_slice_async((md_start - 28)..md_start)
.await?;
let mut buf = &store_footer[4..12];
let offset: u64 = buf.deserialize()?;
offset as usize
} else {
md_start
};
let body = file.read_bytes_slice_async(offset..(file_len - 8)).await?;
let mut data = Vec::with_capacity(file_len - offset);
data.extend_from_slice(body.as_ref());
data.extend_from_slice(md_meta.as_ref());
let data = OwnedBytes::new(data);
let data = OnceCell::new_with(Some(data));
let data = Arc::new(data);
Ok(Footer { data, offset })
}
pub fn data(&self) -> Option<OwnedBytes> {
Some(self.data.get()?.clone())
}
pub async fn get_or_fetch(
&self,
fetch: impl Future<Output = Result<OwnedBytes>>,
) -> Result<OwnedBytes> {
self.data.get_or_try_init(|| fetch).await.cloned()
}
}
impl Debug for Footer {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Footer")
.field("offset", &self.offset)
.finish_non_exhaustive()
}
}