#![forbid(unsafe_code)]
#[cfg(not(target_arch = "wasm32"))]
use std::{fs, io::Write};
use std::{ops::Range, path::Path};
#[cfg(not(target_arch = "wasm32"))]
use tempfile::NamedTempFile;
use crate::{ResourceExt, ResourceStatus, StorageResult, WaitOutcome};
#[derive(Clone, Debug)]
pub struct Atomic<R: ResourceExt> {
inner: R,
}
impl<R: ResourceExt> Atomic<R> {
pub fn new(inner: R) -> Self {
Self { inner }
}
}
impl<R: ResourceExt> ResourceExt for Atomic<R> {
fn write_all(&self, data: &[u8]) -> StorageResult<()> {
self.write_all_inner(data, false)
}
delegate::delegate! {
to self.inner {
fn commit(&self, final_len: Option<u64>) -> StorageResult<()>;
fn fail(&self, reason: String);
fn len(&self) -> Option<u64>;
fn path(&self) -> Option<&Path>;
fn reactivate(&self) -> StorageResult<()>;
fn read_at(&self, offset: u64, buf: &mut [u8]) -> StorageResult<usize>;
fn status(&self) -> ResourceStatus;
fn wait_range(&self, range: Range<u64>) -> StorageResult<WaitOutcome>;
fn write_at(&self, offset: u64, data: &[u8]) -> StorageResult<()>;
}
}
}
impl<R: ResourceExt> Atomic<R> {
pub fn write_all_durable(&self, data: &[u8]) -> StorageResult<()> {
self.write_all_inner(data, true)
}
fn write_all_inner(&self, data: &[u8], durable: bool) -> StorageResult<()> {
#[cfg(not(target_arch = "wasm32"))]
if let Some(path) = self.inner.path() {
let path = path.to_path_buf();
let parent = path.parent().ok_or_else(|| {
crate::StorageError::Failed("atomic write: no parent dir".to_string())
})?;
let _ = fs::create_dir_all(parent);
let mut tmp = NamedTempFile::new_in(parent)
.map_err(|e| crate::StorageError::Failed(format!("atomic write tmpfile: {e}")))?;
Write::write_all(&mut tmp, data)
.map_err(|e| crate::StorageError::Failed(format!("atomic write: {e}")))?;
if durable {
tmp.as_file()
.sync_data()
.map_err(|e| crate::StorageError::Failed(format!("atomic sync_data: {e}")))?;
}
tmp.persist(&path)
.map_err(|e| crate::StorageError::Failed(format!("atomic rename: {e}")))?;
return self.inner.commit(Some(data.len() as u64));
}
let _ = durable;
self.inner.reactivate()?;
self.inner.write_all(data)
}
}