use crate::{
Slice,
fs::{Fs, FsFile},
};
use std::{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: &dyn FsFile, 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 = file.read_at(&mut builder, offset)?;
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], fs: &dyn Fs) -> 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)?;
let _ = &fs;
#[cfg(not(target_os = "windows"))]
{
use crate::fs::FsOpenOptions;
let file = fs.open(path, &FsOpenOptions::new().read(true))?;
FsFile::sync_all(&*file)?;
#[expect(
clippy::expect_used,
reason = "files should always have a parent directory"
)]
let folder = path.parent().expect("should have parent folder");
fs.sync_directory(folder)?;
}
Ok(())
}
#[cfg(not(target_os = "windows"))]
pub fn fsync_directory(path: &Path, fs: &dyn Fs) -> std::io::Result<()> {
fs.sync_directory(path)
}
#[cfg(target_os = "windows")]
pub fn fsync_directory(_path: &Path, _fs: &dyn Fs) -> std::io::Result<()> {
Ok(())
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::useless_vec,
reason = "test code"
)]
mod tests {
use super::*;
use crate::fs::StdFs;
use std::fs::File;
use std::io::Write;
use test_log::test;
#[test]
fn read_exact_short_read_returns_error() -> crate::Result<()> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("short.bin");
{
let mut f = File::create(&path)?;
f.write_all(b"hello")?; }
let file = File::open(&path)?;
let err = read_exact(&file, 0, 10).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof);
Ok(())
}
#[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", &StdFs)?;
let content = std::fs::read_to_string(&path)?;
assert_eq!("newcontent", content);
Ok(())
}
}