use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use crate::error::{Error, Result};
use crate::index::bm25::{Bm25Index, Bm25Snapshot};
pub(crate) const BM25_SNAPSHOT_FILENAME: &str = "bm25.snapshot";
#[must_use]
pub(crate) fn snapshot_path(dir: &Path) -> PathBuf {
dir.join(BM25_SNAPSHOT_FILENAME)
}
pub(crate) fn save_snapshot(dir: &Path, index: &Bm25Index) -> Result<()> {
let snapshot = index.to_snapshot();
let bytes = postcard::to_allocvec(&snapshot)
.map_err(|e| Error::Index(format!("BM25 snapshot serialize: {e}")))?;
let final_path = snapshot_path(dir);
atomic_write(&final_path, &bytes).map_err(|e| Error::Index(format!("BM25 snapshot write: {e}")))
}
pub(crate) fn load_snapshot(dir: &Path) -> Result<Option<Bm25Index>> {
let path = snapshot_path(dir);
let bytes = match std::fs::read(&path) {
Ok(b) => b,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(Error::Index(format!("BM25 snapshot read: {e}"))),
};
let snapshot: Bm25Snapshot = postcard::from_bytes(&bytes)
.map_err(|e| Error::Index(format!("BM25 snapshot deserialize: {e}")))?;
Ok(Some(Bm25Index::from_snapshot(snapshot)))
}
static TMP_COUNTER: AtomicU64 = AtomicU64::new(0);
fn atomic_write(final_path: &Path, data: &[u8]) -> std::io::Result<()> {
let seq = TMP_COUNTER.fetch_add(1, Ordering::Relaxed);
let pid = std::process::id();
let tid = std::thread::current().id();
let file_name = final_path.file_name().unwrap_or_default().to_string_lossy();
let tmp_name = format!("{file_name}.tmp.{pid}.{tid:?}.{seq}");
let tmp_path = final_path.with_file_name(&tmp_name);
let result = atomic_write_inner(&tmp_path, final_path, data);
if result.is_err() {
let _ = std::fs::remove_file(&tmp_path);
}
result
}
fn atomic_write_inner(tmp_path: &Path, final_path: &Path, data: &[u8]) -> std::io::Result<()> {
let file = std::fs::File::create(tmp_path)?;
let mut writer = std::io::BufWriter::new(file);
writer.write_all(data)?;
writer.flush()?;
writer.get_ref().sync_all()?;
std::fs::rename(tmp_path, final_path)
}