From 1cf5f73651d1f6a072bb0f174c0c064e02d41793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20F=C3=A4rnstrand?= Date: Wed, 20 Feb 2019 23:38:49 +0100 Subject: [PATCH] Read write helpers (#896) Provides async versions of read / write helpers being stabilized in `std`. --- src/fs.rs | 1 + tokio-fs/src/lib.rs | 4 +++ tokio-fs/src/read.rs | 75 ++++++++++++++++++++++++++++++++++++++++++ tokio-fs/src/write.rs | 72 ++++++++++++++++++++++++++++++++++++++++ tokio-fs/tests/file.rs | 43 ++++++++++++++++++++---- 5 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 tokio-fs/src/read.rs create mode 100644 tokio-fs/src/write.rs diff --git a/src/fs.rs b/src/fs.rs index 689a60136..e9f050bbb 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -10,3 +10,4 @@ pub use tokio_fs::{create_dir, create_dir_all, file, hard_link, metadata, os, read_dir, read_link}; pub use tokio_fs::{remove_dir, remove_file, rename, set_permissions, symlink_metadata, File}; pub use tokio_fs::OpenOptions; +pub use tokio_fs::{read, write, ReadFile, WriteFile}; diff --git a/tokio-fs/src/lib.rs b/tokio-fs/src/lib.rs index 087078fb6..95e33be1b 100644 --- a/tokio-fs/src/lib.rs +++ b/tokio-fs/src/lib.rs @@ -41,6 +41,7 @@ mod metadata; pub mod os; mod read_dir; mod read_link; +mod read; mod remove_dir; mod remove_file; mod rename; @@ -49,6 +50,7 @@ mod stdin; mod stdout; mod stderr; mod symlink_metadata; +mod write; pub use create_dir::{create_dir, CreateDirFuture}; pub use create_dir_all::{create_dir_all, CreateDirAllFuture}; @@ -58,6 +60,7 @@ pub use hard_link::{hard_link, HardLinkFuture}; pub use metadata::{metadata, MetadataFuture}; pub use read_dir::{read_dir, ReadDirFuture, ReadDir, DirEntry}; pub use read_link::{read_link, ReadLinkFuture}; +pub use read::{read, ReadFile}; pub use remove_dir::{remove_dir, RemoveDirFuture}; pub use remove_file::{remove_file, RemoveFileFuture}; pub use rename::{rename, RenameFuture}; @@ -66,6 +69,7 @@ pub use stdin::{stdin, Stdin}; pub use stdout::{stdout, Stdout}; pub use stderr::{stderr, Stderr}; pub use symlink_metadata::{symlink_metadata, SymlinkMetadataFuture}; +pub use write::{write, WriteFile}; use futures::Poll; use futures::Async::*; diff --git a/tokio-fs/src/read.rs b/tokio-fs/src/read.rs new file mode 100644 index 000000000..7b42ae881 --- /dev/null +++ b/tokio-fs/src/read.rs @@ -0,0 +1,75 @@ +use {file, File}; +use futures::{Async, Future, Poll}; +use std::{io, mem, path::Path}; +use tokio_io; + +/// Creates a future which will open a file for reading and read the entire +/// contents into a buffer and return said buffer. +/// +/// This is the async equivalent of `std::fs::read`. +/// +/// # Examples +/// +/// ```no_run +/// # extern crate tokio; +/// use tokio::prelude::Future; +/// fn main() { +/// let task = tokio::fs::read("foo.txt").map(|data| { +/// // do something with the contents of the file ... +/// println!("foo.txt contains {} bytes", data.len()); +/// }).map_err(|e| { +/// // handle errors +/// eprintln!("IO error: {:?}", e); +/// }); +/// tokio::run(task); +/// } +/// ``` +pub fn read

(path: P) -> ReadFile

+where + P: AsRef + Send + 'static, +{ + ReadFile { + state: State::Open(File::open(path)), + } +} + +/// A future used to open a file and read its entire contents into a buffer. +#[derive(Debug)] +pub struct ReadFile + Send + 'static> { + state: State

, +} + +#[derive(Debug)] +enum State + Send + 'static> { + Open(file::OpenFuture

), + Metadata(file::MetadataFuture), + Read(tokio_io::io::ReadToEnd), +} + +impl + Send + 'static> Future for ReadFile

{ + type Item = Vec; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + let new_state = match &mut self.state { + State::Open(ref mut open_file) => { + let file = try_ready!(open_file.poll()); + State::Metadata(file.metadata()) + } + State::Metadata(read_metadata) => { + let (file, metadata) = try_ready!(read_metadata.poll()); + let buf = Vec::with_capacity(metadata.len() as usize + 1); + let read = tokio_io::io::read_to_end(file, buf); + State::Read(read) + } + State::Read(ref mut read) => { + let (_, buf) = try_ready!(read.poll()); + return Ok(Async::Ready(buf)); + } + }; + + mem::replace(&mut self.state, new_state); + // Getting here means we transitionsed state. Must poll the new state. + self.poll() + } +} diff --git a/tokio-fs/src/write.rs b/tokio-fs/src/write.rs new file mode 100644 index 000000000..17d55552d --- /dev/null +++ b/tokio-fs/src/write.rs @@ -0,0 +1,72 @@ +use {file, File}; +use futures::{Async, Future, Poll}; +use std::{io, mem, path::Path, fmt}; +use tokio_io; + +/// Creates a future that will open a file for writing and write the entire +/// contents of `contents` to it. +/// +/// This is the async equivalent of `std::fs::write`. +/// +/// # Examples +/// +/// ```no_run +/// # extern crate tokio; +/// use tokio::prelude::Future; +/// fn main() { +/// let buffer = b"Hello world!"; +/// let task = tokio::fs::write("foo.txt", buffer).map(|data| { +/// // `data` has now been written to foo.txt. The buffer is being +/// // returned so it can be used for other things. +/// println!("foo.txt now had {} bytes written to it", data.len()); +/// }).map_err(|e| { +/// // handle errors +/// eprintln!("IO error: {:?}", e); +/// }); +/// tokio::run(task); +/// } +/// ``` +pub fn write>(path: P, contents: C) -> WriteFile +where + P: AsRef + Send + 'static, +{ + WriteFile { + state: State::Create(File::create(path), Some(contents)), + } +} + +/// A future used to open a file for writing and write the entire contents +/// of some data to it. +#[derive(Debug)] +pub struct WriteFile + Send + 'static, C: AsRef<[u8]>> { + state: State, +} + +#[derive(Debug)] +enum State + Send + 'static, C: AsRef<[u8]>> { + Create(file::CreateFuture

, Option), + Write(tokio_io::io::WriteAll), +} + +impl + Send + 'static, C: AsRef<[u8]> + fmt::Debug> Future for WriteFile { + type Item = C; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + let new_state = match &mut self.state { + State::Create(ref mut create_file, contents) => { + let file = try_ready!(create_file.poll()); + let write = tokio_io::io::write_all(file, contents.take().unwrap()); + State::Write(write) + } + State::Write(ref mut write) => { + let (_, contents) = try_ready!(write.poll()); + return Ok(Async::Ready(contents)); + } + }; + + mem::replace(&mut self.state, new_state); + // We just entered the Write state, need to poll it before returning. + self.poll() + } +} diff --git a/tokio-fs/tests/file.rs b/tokio-fs/tests/file.rs index 0780dd3bf..8affdc32c 100644 --- a/tokio-fs/tests/file.rs +++ b/tokio-fs/tests/file.rs @@ -12,8 +12,8 @@ use futures::Future; use rand::{distributions, thread_rng, Rng}; use tempfile::Builder as TmpBuilder; -use std::fs::File as StdFile; -use std::io::{Read, SeekFrom}; +use std::fs; +use std::io::SeekFrom; mod pool; @@ -48,11 +48,7 @@ fn read_write() { }) }); - let mut file = StdFile::open(&file_path).unwrap(); - - let mut dst = vec![]; - file.read_to_end(&mut dst).unwrap(); - + let dst = fs::read(&file_path).unwrap(); assert_eq!(dst, contents); pool::run({ @@ -66,6 +62,39 @@ fn read_write() { }); } +#[test] +fn read_write_helpers() { + const NUM_CHARS: usize = 16 * 1_024; + + let dir = TmpBuilder::new() + .prefix("tokio-fs-tests") + .tempdir() + .unwrap(); + let file_path = dir.path().join("read_write_all.txt"); + + let contents: Vec = thread_rng() + .sample_iter(&distributions::Alphanumeric) + .take(NUM_CHARS) + .collect::() + .into(); + + pool::run(write(file_path.clone(), contents.clone()).then(|res| { + let _ = res.unwrap(); + Ok(()) + })); + + let dst = fs::read(&file_path).unwrap(); + assert_eq!(dst, contents); + + pool::run({ + read(file_path).then(move |res| { + let buf = res.unwrap(); + assert_eq!(buf, contents); + Ok(()) + }) + }); +} + #[test] fn metadata() { let dir = TmpBuilder::new()