use std::fs::{File as StdFile, OpenOptions as StdOpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::path::Path;
#[cfg(windows)]
use std::sync::Mutex;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileOpenOptions {
pub read: bool,
pub write: bool,
pub create: bool,
pub append: bool,
pub truncate: bool,
}
impl Default for FileOpenOptions {
fn default() -> Self {
Self::read_only()
}
}
impl FileOpenOptions {
#[must_use]
pub const fn read_only() -> Self {
Self {
read: true,
write: false,
create: false,
append: false,
truncate: false,
}
}
#[must_use]
pub const fn write_only() -> Self {
Self {
read: false,
write: true,
create: true,
append: false,
truncate: true,
}
}
#[must_use]
pub const fn append_only() -> Self {
Self {
read: false,
write: true,
create: true,
append: true,
truncate: false,
}
}
#[must_use]
pub const fn read_write() -> Self {
Self {
read: true,
write: true,
create: true,
append: false,
truncate: false,
}
}
#[must_use]
pub const fn read_write_truncate() -> Self {
Self {
read: true,
write: true,
create: true,
append: false,
truncate: true,
}
}
}
pub struct File {
inner: StdFile,
#[cfg(windows)]
cursor_lock: Mutex<()>,
}
impl File {
pub fn open_with<P: AsRef<Path>>(path: P, options: FileOpenOptions) -> io::Result<Self> {
let mut opts = StdOpenOptions::new();
opts.read(options.read)
.write(options.write)
.create(options.create)
.append(options.append)
.truncate(options.truncate);
let inner = opts.open(path)?;
Ok(Self {
inner,
#[cfg(windows)]
cursor_lock: Mutex::new(()),
})
}
fn with_cursor<T>(&self, operation: impl FnOnce(&StdFile) -> io::Result<T>) -> io::Result<T> {
#[cfg(windows)]
let _cursor = self
.cursor_lock
.lock()
.map_err(|_| io::Error::other("file cursor lock was poisoned"))?;
operation(&self.inner)
}
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
self.with_cursor(|mut file| file.read(buf))
}
pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
self.with_cursor(|mut file| file.read_to_end(buf))
}
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
self.with_cursor(|mut file| file.write(buf))
}
pub fn write_all(&self, buf: &[u8]) -> io::Result<()> {
self.with_cursor(|mut file| file.write_all(buf))
}
pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
self.with_cursor(|mut file| file.seek(pos))
}
pub fn sync_all(&self) -> io::Result<()> {
self.inner.sync_all()
}
pub fn sync_data(&self) -> io::Result<()> {
self.inner.sync_data()
}
pub fn metadata(&self) -> io::Result<std::fs::Metadata> {
self.inner.metadata()
}
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
#[cfg(unix)]
{
use std::os::unix::fs::FileExt;
self.inner.read_at(buf, offset)
}
#[cfg(windows)]
{
use std::os::windows::fs::FileExt;
let _cursor = self
.cursor_lock
.lock()
.map_err(|_| io::Error::other("file cursor lock was poisoned"))?;
let mut file = &self.inner;
let original = file.stream_position()?;
let read = self.inner.seek_read(buf, offset);
file.seek(SeekFrom::Start(original))?;
read
}
#[cfg(not(any(unix, windows)))]
{
let _ = (buf, offset);
Err(io::Error::new(
io::ErrorKind::Unsupported,
"positioned file reads are unsupported on this target",
))
}
}
}