use std::path::Path;
use crate::transaction::conflict::resolve_plan;
use crate::transaction::filesystem::FileSystem;
use crate::transaction::snapshot_store::SnapshotStore;
use crate::transaction::{
SnapshotId, TransactionError, TransactionOptions, TransactionResult, WritePlan,
};
#[derive(Debug, Clone, Copy)]
pub struct TransactionEngine {
private_mode: u32,
}
impl Default for TransactionEngine {
fn default() -> Self {
Self {
private_mode: 0o600,
}
}
}
impl TransactionEngine {
pub fn apply<F>(
self,
plan: &WritePlan,
options: &TransactionOptions,
doctor: F,
) -> Result<TransactionResult, TransactionError>
where
F: FnOnce(&Path) -> Result<(), String>,
{
let filesystem = FileSystem::new(self.private_mode);
let resolved = resolve_plan(plan, options.root(), options.conflict_policy())?;
preflight(&resolved, options)?;
let snapshot_id = SnapshotId::new();
if options.is_dry_run() {
return Ok(TransactionResult {
snapshot_id,
dry_run: true,
writes: resolved.len(),
});
}
FileSystem::prepare_roots(options)?;
let _lock = FileSystem::acquire_lock(options)?;
let snapshots = SnapshotStore::new(filesystem, options);
snapshots.create(snapshot_id, &resolved)?;
for write in resolved.writes() {
let target = options.root().join(write.path.as_path());
if let Err(error) =
filesystem.atomic_write(options.root(), &target, &write.content, write.mode)
{
return match snapshots.restore(snapshot_id) {
Ok(()) => Err(error),
Err(rollback) => Err(TransactionError::RollbackFailed {
message: format!("{error}; {rollback}"),
}),
};
}
}
if let Err(message) = doctor(options.root()) {
return match snapshots.restore(snapshot_id) {
Ok(()) => Err(TransactionError::DoctorFailed { message }),
Err(rollback) => Err(TransactionError::RollbackFailed {
message: format!("{message}; {rollback}"),
}),
};
}
Ok(TransactionResult {
snapshot_id,
dry_run: false,
writes: resolved.len(),
})
}
pub fn rollback(
self,
snapshot_id: SnapshotId,
options: &TransactionOptions,
) -> Result<(), TransactionError> {
let filesystem = FileSystem::new(self.private_mode);
FileSystem::prepare_roots(options)?;
let _lock = FileSystem::acquire_lock(options)?;
SnapshotStore::new(filesystem, options).restore(snapshot_id)
}
}
fn preflight(plan: &WritePlan, options: &TransactionOptions) -> Result<(), TransactionError> {
for write in plan.writes() {
FileSystem::ensure_path_safe(options.root(), write.path.as_path())?;
}
Ok(())
}