use futures::io::{AsyncRead, AsyncSeek, AsyncWrite};
use std::io;
#[cfg(feature = "tokio-providers")]
use std::io::SeekFrom;
#[cfg(feature = "tokio-providers")]
use std::pin::Pin;
#[cfg(feature = "tokio-providers")]
use std::task::{Context, Poll};
#[cfg(feature = "tokio-providers")]
use tokio_util::compat::{Compat, TokioAsyncReadCompatExt};
#[derive(Debug, Clone, Default)]
pub struct OpenOptions {
flags: u8,
}
impl OpenOptions {
const FLAG_READ: u8 = 1 << 0;
const FLAG_WRITE: u8 = 1 << 1;
const FLAG_CREATE: u8 = 1 << 2;
const FLAG_CREATE_NEW: u8 = 1 << 3;
const FLAG_TRUNCATE: u8 = 1 << 4;
const FLAG_APPEND: u8 = 1 << 5;
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn set_flag(mut self, flag: u8, value: bool) -> Self {
if value {
self.flags |= flag;
} else {
self.flags &= !flag;
}
self
}
}
macro_rules! flag_setters {
($($(#[$attr:meta])* $name:ident => $flag:ident),* $(,)?) => {
impl OpenOptions {
$(
$(#[$attr])*
#[must_use]
pub fn $name(self, value: bool) -> Self {
self.set_flag(Self::$flag, value)
}
)*
}
};
}
flag_setters! {
read => FLAG_READ,
write => FLAG_WRITE,
create => FLAG_CREATE,
create_new => FLAG_CREATE_NEW,
truncate => FLAG_TRUNCATE,
append => FLAG_APPEND,
}
impl OpenOptions {
#[must_use]
pub fn is_read(&self) -> bool {
self.flags & Self::FLAG_READ != 0
}
#[must_use]
pub fn is_write(&self) -> bool {
self.flags & Self::FLAG_WRITE != 0
}
#[must_use]
pub fn is_create(&self) -> bool {
self.flags & Self::FLAG_CREATE != 0
}
#[must_use]
pub fn is_create_new(&self) -> bool {
self.flags & Self::FLAG_CREATE_NEW != 0
}
#[must_use]
pub fn is_truncate(&self) -> bool {
self.flags & Self::FLAG_TRUNCATE != 0
}
#[must_use]
pub fn is_append(&self) -> bool {
self.flags & Self::FLAG_APPEND != 0
}
#[must_use]
pub fn read_only() -> Self {
Self::new().read(true)
}
#[must_use]
pub fn create_write() -> Self {
Self::new().write(true).create(true).truncate(true)
}
#[must_use]
pub fn create_new_write() -> Self {
Self::new().write(true).create_new(true)
}
}
pub trait StorageProvider: Clone + Send + Sync + 'static {
type File: StorageFile + 'static;
fn open(
&self,
path: &str,
options: OpenOptions,
) -> impl std::future::Future<Output = io::Result<Self::File>> + Send;
fn exists(&self, path: &str) -> impl std::future::Future<Output = io::Result<bool>> + Send;
fn delete(&self, path: &str) -> impl std::future::Future<Output = io::Result<()>> + Send;
fn rename(
&self,
from: &str,
to: &str,
) -> impl std::future::Future<Output = io::Result<()>> + Send;
}
pub trait StorageFile: AsyncRead + AsyncWrite + AsyncSeek + Unpin + Send + Sync + 'static {
fn sync_all(&self) -> impl std::future::Future<Output = io::Result<()>> + Send;
fn sync_data(&self) -> impl std::future::Future<Output = io::Result<()>> + Send;
fn size(&self) -> impl std::future::Future<Output = io::Result<u64>> + Send;
fn set_len(&self, size: u64) -> impl std::future::Future<Output = io::Result<()>> + Send;
}
#[cfg(feature = "tokio-providers")]
#[derive(Debug, Clone, Default)]
pub struct TokioStorageProvider;
#[cfg(feature = "tokio-providers")]
impl TokioStorageProvider {
#[must_use]
pub fn new() -> Self {
Self
}
}
#[cfg(feature = "tokio-providers")]
impl StorageProvider for TokioStorageProvider {
type File = TokioStorageFile;
async fn open(&self, path: &str, options: OpenOptions) -> io::Result<Self::File> {
let file = tokio::fs::OpenOptions::new()
.read(options.is_read())
.write(options.is_write())
.create(options.is_create())
.create_new(options.is_create_new())
.truncate(options.is_truncate())
.append(options.is_append())
.open(path)
.await?;
Ok(TokioStorageFile {
inner: file.compat(),
})
}
async fn exists(&self, path: &str) -> io::Result<bool> {
match tokio::fs::metadata(path).await {
Ok(_) => Ok(true),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(e),
}
}
async fn delete(&self, path: &str) -> io::Result<()> {
tokio::fs::remove_file(path).await
}
async fn rename(&self, from: &str, to: &str) -> io::Result<()> {
tokio::fs::rename(from, to).await
}
}
#[cfg(feature = "tokio-providers")]
#[derive(Debug)]
pub struct TokioStorageFile {
inner: Compat<tokio::fs::File>,
}
#[cfg(feature = "tokio-providers")]
impl StorageFile for TokioStorageFile {
async fn sync_all(&self) -> io::Result<()> {
self.inner.get_ref().sync_all().await
}
async fn sync_data(&self) -> io::Result<()> {
self.inner.get_ref().sync_data().await
}
async fn size(&self) -> io::Result<u64> {
let metadata = self.inner.get_ref().metadata().await?;
Ok(metadata.len())
}
async fn set_len(&self, size: u64) -> io::Result<()> {
self.inner.get_ref().set_len(size).await
}
}
#[cfg(feature = "tokio-providers")]
impl AsyncRead for TokioStorageFile {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.inner).poll_read(cx, buf)
}
}
#[cfg(feature = "tokio-providers")]
impl AsyncWrite for TokioStorageFile {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.inner).poll_write(cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_close(cx)
}
}
#[cfg(feature = "tokio-providers")]
impl AsyncSeek for TokioStorageFile {
fn poll_seek(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<io::Result<u64>> {
Pin::new(&mut self.inner).poll_seek(cx, pos)
}
}