use crate::Slice;
use std::{fs::File, io::Write, path::Path};
pub const MAGIC_BYTES: [u8; 4] = [b'L', b'S', b'M', 3];
pub const TABLES_FOLDER: &str = "tables";
pub const BLOBS_FOLDER: &str = "blobs";
pub const CURRENT_VERSION_FILE: &str = "current";
pub fn read_exact(file: &File, offset: u64, size: usize) -> std::io::Result<Slice> {
#[expect(unsafe_code, reason = "see safety")]
let mut builder = unsafe { Slice::builder_unzeroed(size) };
{
let bytes_read: usize;
#[cfg(unix)]
{
use std::os::unix::fs::FileExt;
bytes_read = file.read_at(&mut builder, offset)?;
}
#[cfg(windows)]
{
use std::os::windows::fs::FileExt;
bytes_read = file.seek_read(&mut builder, offset)?;
}
#[cfg(not(any(unix, windows)))]
{
compile_error!("unsupported platform");
unimplemented!();
}
if bytes_read != size {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
format!("read_exact({bytes_read}) at {offset} did not read enough bytes {size}; file has length {}", file.metadata()?.len()),
));
}
}
Ok(builder.freeze().into())
}
pub fn rewrite_atomic(path: &Path, content: &[u8]) -> std::io::Result<()> {
#[expect(
clippy::expect_used,
reason = "every file should have a parent directory"
)]
let folder = path.parent().expect("should have a parent");
let mut temp_file = tempfile::NamedTempFile::new_in(folder)?;
temp_file.write_all(content)?;
temp_file.flush()?;
temp_file.as_file_mut().sync_all()?;
temp_file.persist(path)?;
#[cfg(not(target_os = "windows"))]
{
let file = std::fs::File::open(path)?;
file.sync_all()?;
#[expect(
clippy::expect_used,
reason = "files should always have a parent directory"
)]
let folder = path.parent().expect("should have parent folder");
fsync_directory(folder)?;
}
Ok(())
}
#[cfg(not(target_os = "windows"))]
pub fn fsync_directory(path: &Path) -> std::io::Result<()> {
let file = std::fs::File::open(path)?;
debug_assert!(file.metadata()?.is_dir());
file.sync_all()
}
#[cfg(target_os = "windows")]
pub fn fsync_directory(path: &Path) -> std::io::Result<()> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Write;
use test_log::test;
#[test]
fn atomic_rewrite() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("test.txt");
{
let mut file = File::create(&path)?;
write!(file, "asdasdasdasdasd")?;
}
rewrite_atomic(&path, b"newcontent")?;
let content = std::fs::read_to_string(&path)?;
assert_eq!("newcontent", content);
Ok(())
}
}