use crate::align::write_padding;
use crate::config::PageAlignment;
use crate::error::WriteError;
use crate::frame::{close_frame, register_into_frame, Frame};
use crate::sink::{RewindableSink, Sink, WriteCtx};
use crate::types::MAGIC;
use crate::writer::Writer;
#[derive(Debug, Clone)]
pub struct TrailerSnapshot {
pub prefix_len: u64,
pub bytes: Vec<u8>,
}
impl TrailerSnapshot {
pub fn total_len(&self) -> u64 {
self.prefix_len + self.bytes.len() as u64
}
pub fn root_offset(&self) -> u64 {
let n = self.bytes.len();
let mut buf = [0u8; 8];
buf.copy_from_slice(&self.bytes[n - 12..n - 4]);
u64::from_le_bytes(buf)
}
pub fn trailer_offset(&self) -> u64 {
self.total_len() - 12
}
}
pub(crate) struct Checkpoint {
pos: u64,
padding_written: u64,
root_offset: Option<u64>,
frames: Vec<Frame>,
poisoned: bool,
}
impl<S: Sink> Writer<S> {
pub fn snapshot_trailer(&self) -> Result<TrailerSnapshot, WriteError> {
if self.poisoned {
return Err(WriteError::Poisoned);
}
if !self.frames.is_empty() && self.root_offset.is_some() {
return Err(WriteError::MultipleRootValues);
}
let prefix_len = self.pos;
let mut tail: Vec<u8> = Vec::new();
let mut tail_pos = self.pos;
let mut tail_padding = self.padding_written;
let mut scratch = Vec::with_capacity(64);
let mut frames = self.frames.clone();
let mut root: Option<u64> = self.root_offset;
for frame in frames.iter_mut() {
if let Frame::Object(o) = frame {
o.pending_key = None;
}
}
while let Some(frame) = frames.pop() {
let mut ctx = WriteCtx {
sink: &mut tail,
pos: &mut tail_pos,
scratch: &mut scratch,
padding_written: &mut tail_padding,
};
let root_off = close_frame(frame, &self.opts.policy, &mut ctx)?;
if let Some(parent) = frames.last_mut() {
register_into_frame(parent, root_off, &self.opts, &mut ctx)?;
} else {
root = Some(root_off);
}
}
let root = root.ok_or(WriteError::EmptyDocument)?;
if let PageAlignment::Aligned { page_size } = self.opts.policy.align {
let ps = page_size as u64;
let target_mod = ps - 12;
let cur_mod = tail_pos % ps;
let pad = if cur_mod <= target_mod {
target_mod - cur_mod
} else {
ps - cur_mod + target_mod
};
if pad > 0 {
let mut ctx = WriteCtx {
sink: &mut tail,
pos: &mut tail_pos,
scratch: &mut scratch,
padding_written: &mut tail_padding,
};
write_padding(&mut ctx, pad as usize)?;
}
}
let mut trailer = [0u8; 12];
trailer[..8].copy_from_slice(&root.to_le_bytes());
trailer[8..].copy_from_slice(&MAGIC);
tail.extend_from_slice(&trailer);
Ok(TrailerSnapshot {
prefix_len,
bytes: tail,
})
}
}
impl<S: RewindableSink> Writer<S> {
pub fn try_write<F, T, E>(&mut self, f: F) -> Result<T, E>
where
F: FnOnce(&mut Self) -> Result<T, E>,
E: From<WriteError>,
{
if self.poisoned {
return Err(E::from(WriteError::Poisoned));
}
let cp = self.checkpoint();
match f(self) {
Ok(v) => {
drop(cp);
Ok(v)
}
Err(e) => {
if let Err(rb) = self.rollback(cp) {
return Err(E::from(rb));
}
Err(e)
}
}
}
pub(crate) fn checkpoint(&mut self) -> Checkpoint {
Checkpoint {
pos: self.pos,
padding_written: self.padding_written,
root_offset: self.root_offset,
frames: self.frames.clone(),
poisoned: self.poisoned,
}
}
pub(crate) fn rollback(&mut self, cp: Checkpoint) -> Result<(), WriteError> {
if let Err(e) = self.sink.rewind_to(cp.pos) {
self.poisoned = true;
return Err(WriteError::Io(e));
}
self.pos = cp.pos;
self.padding_written = cp.padding_written;
self.root_offset = cp.root_offset;
self.frames = cp.frames;
self.poisoned = cp.poisoned;
Ok(())
}
}