use super::edit::{VersionEdit, replay_edits};
use crate::fs::{Fs, FsOpenOptions, SyncMode};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use crate::io::{Seek, SeekFrom};
use crate::path::Path;
#[cfg(feature = "std")]
use std::io::{Seek, SeekFrom};
#[must_use]
pub fn log_path(folder: &Path, snapshot_id: super::VersionId) -> crate::path::PathBuf {
folder.join(alloc::format!("edits-{snapshot_id}"))
}
pub fn create_log(
fs: &dyn Fs,
folder: &Path,
snapshot_id: super::VersionId,
sync_mode: SyncMode,
) -> crate::Result<()> {
let path = log_path(folder, snapshot_id);
let existed = match fs.metadata(&path) {
Ok(_) => true,
Err(e) if e.kind() == crate::io::ErrorKind::NotFound => false,
Err(e) => return Err(e.into()),
};
let file = fs.open(
&path,
&FsOpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.write_origin(crate::fs::WriteOrigin::Manifest),
)?;
if existed {
file.sync_data_with(sync_mode)?;
}
Ok(())
}
pub fn append_edit(
fs: &dyn Fs,
path: &Path,
edit: &VersionEdit,
scratch: &mut Vec<u8>,
sync_mode: SyncMode,
) -> crate::Result<Option<u64>> {
edit.encode(scratch)?;
if scratch.len() > super::framing::MAX_FRAME_PAYLOAD as usize {
return Ok(None);
}
let mut file = fs
.open(
path,
&FsOpenOptions::new()
.write(true)
.append(true)
.write_origin(crate::fs::WriteOrigin::Manifest),
)
.map_err(crate::Error::from)?;
let before = file.metadata()?.len;
if let Err(e) = super::framing::write_frame(&mut file, scratch) {
if let Err(cut) = file
.set_len(before)
.and_then(|()| file.sync_data_with(sync_mode))
{
log::warn!(
"edit log {}: the part of a failed append may stay ({cut})",
path.display()
);
}
return Err(e);
}
file.sync_data_with(sync_mode).map_err(|e| {
crate::Error::InstallOutcomeUnknown(alloc::boxed::Box::new(crate::Error::from(e)))
})?;
let appended = u64::try_from(super::framing::FRAME_HEADER_LEN + scratch.len())
.map_err(|_| crate::Error::Unrecoverable)?;
Ok(Some(appended))
}
pub fn replay_log(
fs: &dyn Fs,
path: &Path,
mode: crate::config::ManifestRecoveryMode,
) -> crate::Result<Vec<VersionEdit>> {
match fs.open(path, &FsOpenOptions::new().read(true)) {
Ok(mut file) => replay_edits(&mut file, mode),
Err(e) if e.kind() == crate::io::ErrorKind::NotFound => Ok(Vec::new()),
Err(e) => Err(crate::Error::from(e)),
}
}
pub fn log_size(fs: &dyn Fs, path: &Path) -> crate::Result<u64> {
match fs.open(path, &FsOpenOptions::new().read(true)) {
Ok(mut file) => file.seek(SeekFrom::End(0)).map_err(crate::Error::from),
Err(e) if e.kind() == crate::io::ErrorKind::NotFound => Ok(0),
Err(e) => Err(crate::Error::from(e)),
}
}
#[cfg(test)]
#[expect(clippy::expect_used, clippy::indexing_slicing, reason = "test code")]
mod tests;