use super::{
BackupLayoutGuard, JournalLock, JournalLockError, PersistenceError, create_json_durable,
read_json, write_json_durable,
};
use crate::model::attempt_journal::{
AttemptAuthorityRecord, AttemptJournalRecord, AttemptJournalRecordError,
MAX_ATTEMPT_JOURNAL_BYTES, MutationReceiptRequest, ObservationReceiptRequest,
};
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Debug)]
pub struct AttemptJournalGuard<'a> {
layout: &'a BackupLayoutGuard,
_lock: JournalLock,
record: AttemptJournalRecord,
usable: bool,
}
impl<'a> AttemptJournalGuard<'a> {
pub fn create(
layout: &'a BackupLayoutGuard,
authority: AttemptAuthorityRecord,
) -> Result<Self, AttemptJournalError> {
layout.check_root()?;
let path = journal_path(layout, &authority);
let lock = JournalLock::acquire(&path)?;
let record = AttemptJournalRecord::new(authority);
check_size(&record)?;
create_json_durable(&path, &record)?;
Ok(Self {
layout,
_lock: lock,
record,
usable: true,
})
}
pub fn open(
layout: &'a BackupLayoutGuard,
expected: &AttemptAuthorityRecord,
) -> Result<Self, AttemptJournalError> {
layout.check_root()?;
let path = journal_path(layout, expected);
let lock = JournalLock::acquire(&path)?;
let record: AttemptJournalRecord = read_json(&path, MAX_ATTEMPT_JOURNAL_BYTES)?;
check_size(&record)?;
if record.authority() != expected {
return Err(AttemptJournalError::AuthorityMismatch);
}
Ok(Self {
layout,
_lock: lock,
record,
usable: true,
})
}
pub fn record(&self) -> Result<&AttemptJournalRecord, AttemptJournalError> {
self.check_usable()?;
Ok(&self.record)
}
#[must_use]
pub fn path(&self) -> PathBuf {
journal_path(self.layout, self.record.authority())
}
pub fn reserve_mutation(&mut self) -> Result<u32, AttemptJournalError> {
self.reserve_with(AttemptJournalRecord::reserve_mutation, write_json_durable)
}
pub fn reserve_observation(
&mut self,
mutation: u32,
request: &str,
) -> Result<u32, AttemptJournalError> {
self.reserve_with(
|record| record.reserve_observation(mutation, request),
write_json_durable,
)
}
pub fn record_mutation(
&mut self,
receipt: MutationReceiptRequest,
) -> Result<(), AttemptJournalError> {
self.reserve_with(|record| record.record_mutation(receipt), write_json_durable)
}
pub fn record_observation(
&mut self,
receipt: ObservationReceiptRequest,
) -> Result<(), AttemptJournalError> {
self.reserve_with(
|record| record.record_observation(receipt),
write_json_durable,
)
}
fn reserve_with<T>(
&mut self,
transition: impl FnOnce(&mut AttemptJournalRecord) -> Result<T, AttemptJournalRecordError>,
write: impl FnOnce(&Path, &AttemptJournalRecord) -> Result<(), PersistenceError>,
) -> Result<T, AttemptJournalError> {
self.check_usable()?;
let mut next = self.record.clone();
let result = transition(&mut next)?;
check_size(&next)?;
self.usable = false;
write(&self.path(), &next)?;
self.record = next;
self.usable = true;
Ok(result)
}
fn check_usable(&self) -> Result<(), AttemptJournalError> {
if !self.usable {
return Err(AttemptJournalError::IndeterminateWrite);
}
self.layout.check_root()?;
Ok(())
}
}
fn journal_path(layout: &BackupLayoutGuard, authority: &AttemptAuthorityRecord) -> PathBuf {
layout.root().join(format!(
"attempt-{}.json",
authority.binding().operation_sequence()
))
}
fn check_size(record: &AttemptJournalRecord) -> Result<(), PersistenceError> {
if serde_json::to_vec_pretty(record)?.len() as u64 > MAX_ATTEMPT_JOURNAL_BYTES {
return Err(PersistenceError::RecordTooLarge {
limit: MAX_ATTEMPT_JOURNAL_BYTES,
});
}
Ok(())
}
#[derive(Debug, Error)]
pub enum AttemptJournalError {
#[error("attempt journal authority mismatch")]
AuthorityMismatch,
#[error("attempt journal write outcome indeterminate; reopen retained evidence")]
IndeterminateWrite,
#[error(transparent)]
Record(#[from] AttemptJournalRecordError),
#[error(transparent)]
Lock(#[from] JournalLockError),
#[error(transparent)]
Persistence(#[from] PersistenceError),
}
#[cfg(all(test, unix))]
mod tests;