#![forbid(missing_docs)]
#![cfg_attr(test, deny(warnings))]
#![doc(test(attr(deny(warnings))))]
#[cfg(not(any(feature = "async-std", feature = "tokio")))]
compile_error!(
"Either feature `random-access-disk/async-std` or `random-access-disk/tokio` must be enabled."
);
#[cfg(all(feature = "async-std", feature = "tokio"))]
compile_error!("features `random-access-disk/async-std` and `random-access-disk/tokio` are mutually exclusive");
#[cfg(feature = "async-std")]
use async_std::{
fs::{self, OpenOptions},
io::prelude::{SeekExt, WriteExt},
io::{ReadExt, SeekFrom},
};
use random_access_storage::{RandomAccess, RandomAccessError};
use std::ops::Drop;
use std::path;
#[cfg(feature = "tokio")]
use std::io::SeekFrom;
#[cfg(feature = "tokio")]
use tokio::{
fs::{self, OpenOptions},
io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt},
};
#[cfg(all(
feature = "sparse",
any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "macos",
)
))]
mod unix;
#[cfg(all(
feature = "sparse",
any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "macos",
)
))]
use unix::{get_length_and_block_size, set_sparse, trim};
#[cfg(all(feature = "sparse", windows))]
mod windows;
#[cfg(all(feature = "sparse", windows))]
use windows::{get_length_and_block_size, set_sparse, trim};
#[cfg(not(all(
feature = "sparse",
any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "macos",
windows,
)
)))]
mod default;
#[cfg(not(all(
feature = "sparse",
any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "macos",
windows,
)
)))]
use default::{get_length_and_block_size, set_sparse, trim};
#[derive(Debug)]
pub struct RandomAccessDisk {
#[allow(dead_code)]
filename: path::PathBuf,
file: Option<fs::File>,
length: u64,
block_size: u64,
auto_sync: bool,
}
impl RandomAccessDisk {
#[allow(clippy::new_ret_no_self)]
pub async fn open(
filename: impl AsRef<path::Path>,
) -> Result<RandomAccessDisk, RandomAccessError> {
Self::builder(filename).build().await
}
pub fn builder(filename: impl AsRef<path::Path>) -> Builder {
Builder::new(filename)
}
}
#[async_trait::async_trait]
impl RandomAccess for RandomAccessDisk {
async fn write(
&mut self,
offset: u64,
data: &[u8],
) -> Result<(), RandomAccessError> {
let file = self.file.as_mut().expect("self.file was None.");
file.seek(SeekFrom::Start(offset)).await?;
file.write_all(data).await?;
if self.auto_sync {
file.sync_all().await?;
}
let new_len = offset + (data.len() as u64);
if new_len > self.length {
self.length = new_len;
}
Ok(())
}
async fn read(
&mut self,
offset: u64,
length: u64,
) -> Result<Vec<u8>, RandomAccessError> {
if offset + length > self.length {
return Err(RandomAccessError::OutOfBounds {
offset,
end: Some(offset + length),
length: self.length,
});
}
let file = self.file.as_mut().expect("self.file was None.");
let mut buffer = vec![0; length as usize];
file.seek(SeekFrom::Start(offset)).await?;
let _bytes_read = file.read(&mut buffer[..]).await?;
Ok(buffer)
}
async fn del(
&mut self,
offset: u64,
length: u64,
) -> Result<(), RandomAccessError> {
if offset > self.length {
return Err(RandomAccessError::OutOfBounds {
offset,
end: None,
length: self.length,
});
};
if length == 0 {
return Ok(());
}
if offset + length >= self.length {
return self.truncate(offset).await;
}
let file = self.file.as_mut().expect("self.file was None.");
trim(file, offset, length, self.block_size).await?;
if self.auto_sync {
file.sync_all().await?;
}
Ok(())
}
async fn truncate(&mut self, length: u64) -> Result<(), RandomAccessError> {
let file = self.file.as_ref().expect("self.file was None.");
self.length = length;
file.set_len(self.length).await?;
if self.auto_sync {
file.sync_all().await?;
}
Ok(())
}
async fn len(&mut self) -> Result<u64, RandomAccessError> {
Ok(self.length)
}
async fn is_empty(&mut self) -> Result<bool, RandomAccessError> {
Ok(self.length == 0)
}
async fn sync_all(&mut self) -> Result<(), RandomAccessError> {
if !self.auto_sync {
let file = self.file.as_ref().expect("self.file was None.");
file.sync_all().await?;
}
Ok(())
}
}
impl Drop for RandomAccessDisk {
fn drop(&mut self) {
#[cfg(feature = "async-std")]
if let Some(file) = &self.file {
let _ = async_std::task::block_on(file.sync_all());
}
}
}
pub struct Builder {
filename: path::PathBuf,
auto_sync: bool,
}
impl Builder {
pub fn new(filename: impl AsRef<path::Path>) -> Self {
Self {
filename: filename.as_ref().into(),
auto_sync: true,
}
}
#[cfg(feature = "async-std")]
pub fn auto_sync(mut self, auto_sync: bool) -> Self {
self.auto_sync = auto_sync;
self
}
pub async fn build(self) -> Result<RandomAccessDisk, RandomAccessError> {
if let Some(dirname) = self.filename.parent() {
mkdirp::mkdirp(dirname)?;
}
let mut file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(&self.filename)
.await?;
file.sync_all().await?;
set_sparse(&mut file).await?;
let (length, block_size) = get_length_and_block_size(&file).await?;
Ok(RandomAccessDisk {
filename: self.filename,
file: Some(file),
length,
auto_sync: self.auto_sync,
block_size,
})
}
}