#![deny(missing_debug_implementations, missing_docs, unsafe_code)]
use std::fmt;
use std::fs;
use std::future::Future;
use std::io;
use std::path::Path;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use futures::channel::oneshot::{self, Receiver};
use futures::executor::ThreadPool;
use futures::task::{Spawn, SpawnExt};
pub use self::read::{FsReadStream, ReadOptions};
pub use self::write::{FsWriteSink, WriteOptions};
mod read;
mod write;
#[derive(Clone)]
pub struct FsPool {
executor: Arc<dyn Spawn + Send + Sync>,
}
impl FsPool {
pub fn new(threads: usize) -> Self {
assert!(
threads > 0,
"filesystem worker count must be greater than zero"
);
let executor = ThreadPool::builder()
.pool_size(threads)
.name_prefix("futures-fs-")
.create()
.expect("failed to create filesystem worker pool");
Self::with_executor(executor)
}
pub fn with_executor<E>(executor: E) -> Self
where
E: Spawn + Send + Sync + 'static,
{
Self {
executor: Arc::new(executor),
}
}
#[doc(hidden)]
#[deprecated(since = "0.0.5", note = "use FsPool::with_executor")]
pub fn from_executor<E>(executor: E) -> Self
where
E: Spawn + Send + Sync + 'static,
{
Self::with_executor(executor)
}
pub fn read<P>(&self, path: P, opts: ReadOptions) -> FsReadStream
where
P: AsRef<Path>,
{
read::new(self, path.as_ref().to_owned(), opts)
}
pub fn read_file(&self, file: fs::File, opts: ReadOptions) -> FsReadStream {
read::new_from_file(self, file, opts)
}
pub fn write<P>(&self, path: P, opts: WriteOptions) -> FsWriteSink
where
P: AsRef<Path>,
{
write::new(self, path.as_ref().to_owned(), opts)
}
pub fn write_file(&self, file: fs::File) -> FsWriteSink {
write::new_from_file(self, file)
}
pub fn delete<P>(&self, path: P) -> FsFuture<()>
where
P: AsRef<Path>,
{
let path = path.as_ref().to_owned();
self.spawn(move || fs::remove_file(path))
}
pub(crate) fn spawn<T, F>(&self, operation: F) -> FsFuture<T>
where
T: Send + 'static,
F: FnOnce() -> io::Result<T> + Send + 'static,
{
let (sender, receiver) = oneshot::channel();
let task = async move {
let _ = sender.send(operation());
};
let _ = self.executor.spawn(task);
FsFuture { inner: receiver }
}
}
impl Default for FsPool {
fn default() -> Self {
Self::new(4)
}
}
impl fmt::Debug for FsPool {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FsPool").finish()
}
}
#[must_use = "futures do nothing unless polled or awaited"]
pub struct FsFuture<T> {
inner: Receiver<io::Result<T>>,
}
impl<T> Future for FsFuture<T> {
type Output = io::Result<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
match Pin::new(&mut this.inner).poll(cx) {
Poll::Ready(Ok(result)) => Poll::Ready(result),
Poll::Ready(Err(_)) => Poll::Ready(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"filesystem worker pool stopped before completing the operation",
))),
Poll::Pending => Poll::Pending,
}
}
}
impl<T> fmt::Debug for FsFuture<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FsFuture").finish()
}
}