use crate::error::Error;
use crate::format::FileFormat;
use crate::fs::{File, OpenOptions};
use super::{FileManager, FileLock};
use std::io;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct AtomicManager<Format, Support> {
format: Format,
options: AtomicManagerOptions<Support>,
file: File
}
impl<T, Format, Support> FileManager<T> for AtomicManager<Format, Support>
where Format: FileFormat<T>, Support: AtomicFileSupport {
type Format = Format;
type Options = AtomicManagerOptions<Support>;
type Error = Error<<Self::Format as FileFormat<T>>::FormatError>;
fn open<P: AsRef<Path>>(
path: P, format: Self::Format, options: Self::Options
) -> Result<Self, Self::Error> {
let file = options.open(path.as_ref())?;
Ok(AtomicManager { format, options, file })
}
fn read(&mut self) -> Result<T, Self::Error> {
super::read(&self.format, &self.file)
}
fn write(&mut self, value: &T) -> Result<(), Self::Error> {
self.options.write_atomic(&self.format, &mut self.file, value)
}
fn into_inner(self) -> Result<(Self::Format, Self::Options), Self::Error> {
self.options.lock.unlock(&self.file)?;
Ok((self.format, self.options))
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Default)]
pub struct AtomicManagerOptions<Support> {
pub lock: FileLock,
pub support: Support
}
impl<Support> AtomicManagerOptions<Support> where Support: AtomicFileSupport {
pub const fn new(support: Support) -> Self {
AtomicManagerOptions { lock: FileLock::None, support }
}
pub const fn with_lock(mut self, lock: FileLock) -> Self {
self.lock = lock;
self
}
pub fn open<P: Into<PathBuf>>(&self, path: P) -> io::Result<File> {
let file = OpenOptions::new()
.read(true).write(true)
.open(path)?;
self.lock.lock(&file)?;
Ok(file)
}
pub fn create<P: Into<PathBuf>>(&self, path: P) -> io::Result<File> {
let file = OpenOptions::new()
.read(true).write(true)
.create(true).truncate(false)
.open(path)?;
self.lock.lock(&file)?;
Ok(file)
}
pub fn create_new<P: Into<PathBuf>>(&self, path: P) -> io::Result<File> {
let file = OpenOptions::new()
.read(true).write(true)
.create_new(true)
.open(path)?;
self.lock.lock(&file)?;
Ok(file)
}
pub fn write_atomic<T, Format>(&mut self, format: &Format, file: &mut File, value: &T) -> Result<(), Error<Format::FormatError>>
where Format: FileFormat<T> {
let new_file_path = self.support.pick_temporary_file_location(file.path())?;
let new_file = self.create_new(new_file_path)?;
format.to_writer_buffered(&new_file, value)
.map_err(Error::Format)?;
new_file.sync_all()?;
replace_file(file, new_file)?;
Ok(())
}
}
pub trait AtomicFileSupport {
fn pick_temporary_file_location(&mut self, current: &Path) -> io::Result<PathBuf>;
}
fn replace_file(file_dest: &mut File, file_src: File) -> io::Result<()> {
crate::fs::rename(file_src.path(), file_dest.path())?;
*file_dest.file_mut() = file_src.into_file();
Ok(())
}