#[cfg_attr(docsrs, doc(cfg(feature = "atomic")))]
#[cfg(feature = "atomic")]
pub mod atomic;
pub mod standard;
use crate::error::{Error, OtherError};
use crate::format::FileFormat;
use crate::fs::{File, OpenOptions};
use std::io::{self, prelude::*, SeekFrom};
use std::path::{Path, PathBuf};
pub trait FileManager<T>: Sized {
type Format: FileFormat<T>;
type Options;
type Error: std::error::Error + From<Error<<Self::Format as FileFormat<T>>::FormatError>>;
fn open<P: AsRef<Path>>(
path: P, format: Self::Format, options: Self::Options
) -> Result<Self, Self::Error>;
fn create_overwrite<P: AsRef<Path>>(
path: P, format: Self::Format, options: Self::Options, value: T
) -> Result<(T, Self), Self::Error> {
overwrite(path.as_ref(), &format, &value)?;
Ok((value, Self::open(path, format, options)?))
}
fn create_or<P: AsRef<Path>>(
path: P, format: Self::Format, options: Self::Options, value: T
) -> Result<(T, Self), Self::Error> {
Self::create_or_else(path, format, options, || value)
}
fn create_or_else<P: AsRef<Path>>(
path: P, format: Self::Format, options: Self::Options, closure: impl FnOnce() -> T
) -> Result<(T, Self), Self::Error> {
let value = read_or_write(path.as_ref(), &format, closure)?;
Ok((value, Self::open(path, format, options)?))
}
fn create_or_default<P: AsRef<Path>>(
path: P, format: Self::Format, options: Self::Options
) -> Result<(T, Self), Self::Error>
where T: Default {
Self::create_or_else(path, format, options, T::default)
}
fn read(&mut self) -> Result<T, Self::Error>;
fn write(&mut self, value: &T) -> Result<(), Self::Error>;
fn into_inner(self) -> Result<(Self::Format, Self::Options), Self::Error>;
fn close(self) -> Result<(), Self::Error> {
self.into_inner().map(drop)
}
}
fn read_or_write<T, C, Format>(path: &Path, format: &Format, closure: C) -> Result<T, Error<Format::FormatError>>
where Format: FileFormat<T>, C: FnOnce() -> T {
use std::io::ErrorKind::NotFound;
match OpenOptions::new().read(true).open(path) {
Ok(file) => read(format, &file),
Err(err) if err.kind() == NotFound => {
let value = closure();
overwrite(path, format, &value)?;
Ok(value)
},
Err(err) => Err(err.into())
}
}
fn overwrite<T, Format>(path: &Path, format: &Format, value: &T) -> Result<(), Error<Format::FormatError>>
where Format: FileFormat<T> {
let file = OpenOptions::new()
.write(true).create(true)
.truncate(true)
.open(path)?;
write(format, &file, value)?;
Ok(())
}
pub fn read<T, Format>(format: &Format, mut file: &File) -> Result<T, Error<Format::FormatError>>
where Format: FileFormat<T> {
file.seek(SeekFrom::Start(0))?;
let value = format.from_reader_buffered(file)
.map_err(Error::Format)?;
Ok(value)
}
pub fn write<T, Format>(format: &Format, mut file: &File, value: &T) -> Result<(), Error<Format::FormatError>>
where Format: FileFormat<T> {
file.set_len(0)?;
file.seek(SeekFrom::Start(0))?;
format.to_writer_buffered(file, value)
.map_err(Error::Format)?;
file.sync_all()?;
Ok(())
}
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum FileMode {
Readonly,
#[default]
Writable
}
impl FileMode {
pub fn open<P: Into<PathBuf> + AsRef<Path>>(self, path: P) -> io::Result<File> {
OpenOptions::new()
.read(self.can_read())
.write(self.can_write())
.open(path)
}
pub fn create<P: Into<PathBuf> + AsRef<Path>>(self, path: P) -> io::Result<File> {
OpenOptions::new()
.read(self.can_read())
.write(self.can_write())
.create(true).truncate(false)
.open(path)
}
pub fn create_new<P: Into<PathBuf> + AsRef<Path>>(self, path: P) -> io::Result<File> {
OpenOptions::new()
.read(self.can_read())
.write(self.can_write())
.create_new(true)
.open(path)
}
pub const fn can_read(self) -> bool {
match self {
Self::Readonly | Self::Writable => true
}
}
pub const fn can_write(self) -> bool {
match self {
Self::Readonly => false,
Self::Writable => true
}
}
pub const fn must_read(self) -> Result<(), OtherError> {
if self.can_read() { Ok(()) } else { Err(OtherError::IncompatibleFileMode(self)) }
}
pub const fn must_write(self) -> Result<(), OtherError> {
if self.can_write() { Ok(()) } else { Err(OtherError::IncompatibleFileMode(self)) }
}
}
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum FileLock {
#[default]
None,
Shared,
Exclusive
}
impl FileLock {
pub fn lock(self, file: &File) -> io::Result<()> {
match self {
Self::None => Ok(()),
Self::Shared => crate::fs::FileExt::lock_shared(file),
Self::Exclusive => crate::fs::FileExt::lock(file)
}
}
pub fn try_lock(self, file: &File) -> Result<(), crate::fs::TryLockError> {
match self {
Self::None => Ok(()),
Self::Shared => crate::fs::FileExt::try_lock_shared(file),
Self::Exclusive => crate::fs::FileExt::try_lock(file)
}
}
pub fn unlock(self, file: &File) -> io::Result<()> {
match self {
Self::None => Ok(()),
Self::Shared => crate::fs::FileExt::unlock(file),
Self::Exclusive => crate::fs::FileExt::unlock(file)
}
}
}