use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub struct RkyvStore {
pub path: PathBuf,
pub bytes: Arc<[u8]>,
}
const MAX_STRING_HITS: usize = 20_000;
const STRINGS_SCAN_CAP: usize = 64 * 1024 * 1024;
#[derive(Debug, Default)]
pub struct Strings {
pub hits: Vec<StringHit>,
pub truncated: bool,
pub scanned: usize,
}
#[derive(Debug)]
pub struct StringHit {
pub offset: usize,
pub text: String,
}
impl RkyvStore {
pub fn open(path: &Path) -> Result<Self> {
let bytes = std::fs::read(path).with_context(|| format!("read {}", path.display()))?;
Ok(Self {
path: path.to_path_buf(),
bytes: bytes.into(),
})
}
pub fn len(&self) -> usize {
self.bytes.len()
}
pub fn strings(&self, min_len: usize) -> Strings {
let scan_end = self.bytes.len().min(STRINGS_SCAN_CAP);
let mut hits = Vec::new();
let mut start: Option<usize> = None;
for (i, &b) in self.bytes[..scan_end].iter().enumerate() {
if hits.len() >= MAX_STRING_HITS {
break;
}
let printable = (0x20..0x7f).contains(&b);
match (printable, start) {
(true, None) => start = Some(i),
(false, Some(s)) => {
if i - s >= min_len {
hits.push(StringHit {
offset: s,
text: String::from_utf8_lossy(&self.bytes[s..i]).into_owned(),
});
}
start = None;
}
_ => {}
}
}
if let Some(s) = start {
if hits.len() < MAX_STRING_HITS && scan_end - s >= min_len {
hits.push(StringHit {
offset: s,
text: String::from_utf8_lossy(&self.bytes[s..scan_end]).into_owned(),
});
}
}
let truncated = hits.len() >= MAX_STRING_HITS || scan_end < self.bytes.len();
Strings {
hits,
truncated,
scanned: scan_end,
}
}
pub fn hex_row(&self, offset: usize) -> String {
let end = (offset + 16).min(self.bytes.len());
let chunk = &self.bytes[offset.min(self.bytes.len())..end];
crate::hexedit::hex_dump_line(offset, chunk)
}
}