diff --git a/Cargo.toml b/Cargo.toml index 24c687f..ce14c52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,5 +16,14 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] +async-std = { version = "1.12.0", optional = true } +async-trait = { version = "0.1.77", optional = true } chksum-hash-core = "0.0.0" +futures-lite = { version = "2.2.0", optional = true } thiserror = "1.0.51" +tokio = { version = "1.36.0", optional = true, features = ["fs", "io-util"] } + +[features] +default = [] +async-std = ["async-trait", "dep:async-std", "futures-lite"] +tokio = ["async-trait", "dep:tokio", "async-std?/tokio1"] diff --git a/src/async_std.rs b/src/async_std.rs new file mode 100644 index 0000000..ddc4343 --- /dev/null +++ b/src/async_std.rs @@ -0,0 +1,311 @@ +#[cfg(not(feature = "tokio"))] +use std::path::{Path, PathBuf}; + +use async_std::fs::{metadata, read_dir, DirEntry, File, ReadDir}; +use async_std::io::BufReader; +use async_std::path::{Path as AsyncPath, PathBuf as AsyncPathBuf}; +use async_std::stream::StreamExt; +use async_trait::async_trait; +use futures_lite::io::AsyncBufReadExt; + +use crate::{AsyncChksumable, Hash, Hashable, Result}; + +#[async_trait] +impl AsyncChksumable for AsyncPath { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let metadata = metadata(&self).await?; + if metadata.is_dir() { + read_dir(self).await?.chksum_with(hash).await + } else { + // everything treat as a file when it is not a directory + File::open(self).await?.chksum_with(hash).await + } + } +} + +#[async_trait] +impl AsyncChksumable for &AsyncPath { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let metadata = metadata(&self).await?; + if metadata.is_dir() { + read_dir(self).await?.chksum_with(hash).await + } else { + // everything treat as a file when it is not a directory + File::open(self).await?.chksum_with(hash).await + } + } +} + +#[async_trait] +impl AsyncChksumable for &mut AsyncPath { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let metadata = metadata(&self).await?; + if metadata.is_dir() { + read_dir(self).await?.chksum_with(hash).await + } else { + // everything treat as a file when it is not a directory + File::open(self).await?.chksum_with(hash).await + } + } +} + +#[async_trait] +impl AsyncChksumable for AsyncPathBuf { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.as_path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for &AsyncPathBuf { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.as_path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for &mut AsyncPathBuf { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.as_path().chksum_with(hash).await + } +} + +#[cfg(not(feature = "tokio"))] +#[async_trait] +impl AsyncChksumable for Path { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let metadata = metadata(&self).await?; + if metadata.is_dir() { + read_dir(self).await?.chksum_with(hash).await + } else { + // everything treat as a file when it is not a directory + File::open(self).await?.chksum_with(hash).await + } + } +} + +#[cfg(not(feature = "tokio"))] +#[async_trait] +impl AsyncChksumable for &Path { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let metadata = metadata(&self).await?; + if metadata.is_dir() { + read_dir(self).await?.chksum_with(hash).await + } else { + // everything treat as a file when it is not a directory + File::open(self).await?.chksum_with(hash).await + } + } +} + +#[cfg(not(feature = "tokio"))] +#[async_trait] +impl AsyncChksumable for &mut Path { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let metadata = metadata(&self).await?; + if metadata.is_dir() { + read_dir(self).await?.chksum_with(hash).await + } else { + // everything treat as a file when it is not a directory + File::open(self).await?.chksum_with(hash).await + } + } +} + +#[cfg(not(feature = "tokio"))] +#[async_trait] +impl AsyncChksumable for PathBuf { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.as_path().chksum_with(hash).await + } +} + +#[cfg(not(feature = "tokio"))] +#[async_trait] +impl AsyncChksumable for &PathBuf { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.as_path().chksum_with(hash).await + } +} + +#[cfg(not(feature = "tokio"))] +#[async_trait] +impl AsyncChksumable for &mut PathBuf { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.as_path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for File { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + // if self.is_terminal() { + // return Err(Error::IsTerminal); + // } + + let mut reader = BufReader::new(self); + loop { + let buffer = reader.fill_buf().await?; + let length = buffer.len(); + if length == 0 { + break; + } + buffer.hash_with(hash); + reader.consume(length); + } + Ok(()) + } +} + +#[async_trait] +impl AsyncChksumable for &File { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + // if self.is_terminal() { + // return Err(Error::IsTerminal); + // } + + let mut reader = BufReader::new(self); + loop { + let buffer = reader.fill_buf().await?; + let length = buffer.len(); + if length == 0 { + break; + } + buffer.hash_with(hash); + reader.consume(length); + } + Ok(()) + } +} + +#[async_trait] +impl AsyncChksumable for &mut File { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + // if self.is_terminal() { + // return Err(Error::IsTerminal); + // } + + let mut reader = BufReader::new(self); + loop { + let buffer = reader.fill_buf().await?; + let length = buffer.len(); + if length == 0 { + break; + } + buffer.hash_with(hash); + reader.consume(length); + } + Ok(()) + } +} + +#[async_trait] +impl AsyncChksumable for DirEntry { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for &DirEntry { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for &mut DirEntry { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for ReadDir { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let mut dir_entries = Vec::new(); + while let Some(dir_entry) = self.next().await { + dir_entries.push(dir_entry?); + } + dir_entries.sort_by_key(DirEntry::path); + for mut dir_entry in dir_entries { + AsyncChksumable::chksum_with(&mut dir_entry, hash).await?; + } + Ok(()) + } +} + +#[async_trait] +impl AsyncChksumable for &mut ReadDir { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let mut dir_entries = Vec::new(); + while let Some(dir_entry) = self.next().await { + dir_entries.push(dir_entry?); + } + dir_entries.sort_by_key(DirEntry::path); + for mut dir_entry in dir_entries { + AsyncChksumable::chksum_with(&mut dir_entry, hash).await?; + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 98aa09a..fa944e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,13 +31,19 @@ //! //! This crate is licensed under the MIT License. +#[cfg(feature = "async-std")] +mod async_std; mod error; +#[cfg(feature = "tokio")] +mod tokio; use std::fmt::{Display, LowerHex, UpperHex}; use std::fs::{read_dir, DirEntry, File, ReadDir}; use std::io::{self, BufRead, BufReader, IsTerminal, Stdin, StdinLock}; use std::path::{Path, PathBuf}; +#[cfg(any(feature = "async-std", feature = "tokio"))] +use async_trait::async_trait; #[doc(no_inline)] pub use chksum_hash_core as hash; @@ -68,6 +74,15 @@ where data.chksum::() } +/// Computes the hash of the given input. +#[cfg(any(feature = "async-std", feature = "tokio"))] +pub async fn async_chksum(mut data: impl AsyncChksumable + Send) -> Result +where + T: Hash + Send, +{ + data.chksum::().await +} + /// A trait for hash digests. pub trait Digest: Display { #[must_use] @@ -236,7 +251,7 @@ impl Chksumable for PathBuf { where H: Hash, { - self.as_path().chksum_with(hash) + Chksumable::chksum_with(&mut self.as_path(), hash) } } @@ -245,7 +260,7 @@ impl Chksumable for &PathBuf { where H: Hash, { - self.as_path().chksum_with(hash) + Chksumable::chksum_with(&mut self.as_path(), hash) } } @@ -254,7 +269,7 @@ impl Chksumable for &mut PathBuf { where H: Hash, { - self.as_path().chksum_with(hash) + Chksumable::chksum_with(&mut self.as_path(), hash) } } @@ -332,7 +347,7 @@ impl Chksumable for DirEntry { where H: Hash, { - self.path().chksum_with(hash) + Chksumable::chksum_with(&mut self.path(), hash) } } @@ -341,7 +356,7 @@ impl Chksumable for &DirEntry { where H: Hash, { - self.path().chksum_with(hash) + Chksumable::chksum_with(&mut self.path(), hash) } } @@ -350,7 +365,7 @@ impl Chksumable for &mut DirEntry { where H: Hash, { - self.path().chksum_with(hash) + Chksumable::chksum_with(&mut self.path(), hash) } } @@ -454,3 +469,36 @@ impl Chksumable for &mut StdinLock<'_> { Ok(()) } } + +/// A trait for complex objects which must be processed chunk by chunk. +#[cfg(any(feature = "async-std", feature = "tokio"))] +#[async_trait] +pub trait AsyncChksumable { + async fn chksum(&mut self) -> Result + where + H: Hash + Send, + { + let mut hash = H::default(); + self.chksum_with(&mut hash).await?; + Ok(hash.digest()) + } + + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send; +} + +#[cfg(any(feature = "async-std", feature = "tokio"))] +#[async_trait] +impl AsyncChksumable for T +where + T: Hashable + Send, +{ + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.hash_with(hash); + Ok(()) + } +} diff --git a/src/tokio.rs b/src/tokio.rs new file mode 100644 index 0000000..96a0273 --- /dev/null +++ b/src/tokio.rs @@ -0,0 +1,223 @@ +use std::path::{Path, PathBuf}; + +use async_trait::async_trait; +use tokio::fs::{metadata, read_dir, DirEntry, File, ReadDir}; +use tokio::io::{AsyncBufReadExt as _, BufReader}; + +use crate::{AsyncChksumable, Hash, Hashable, Result}; + +#[async_trait] +impl AsyncChksumable for Path { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let metadata = metadata(&self).await?; + if metadata.is_dir() { + read_dir(self).await?.chksum_with(hash).await + } else { + // everything treat as a file when it is not a directory + File::open(self).await?.chksum_with(hash).await + } + } +} + +#[async_trait] +impl AsyncChksumable for &Path { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let metadata = metadata(&self).await?; + if metadata.is_dir() { + read_dir(self).await?.chksum_with(hash).await + } else { + // everything treat as a file when it is not a directory + File::open(self).await?.chksum_with(hash).await + } + } +} + +#[async_trait] +impl AsyncChksumable for &mut Path { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let metadata = metadata(&self).await?; + if metadata.is_dir() { + read_dir(self).await?.chksum_with(hash).await + } else { + // everything treat as a file when it is not a directory + File::open(self).await?.chksum_with(hash).await + } + } +} + +#[async_trait] +impl AsyncChksumable for PathBuf { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.as_path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for &PathBuf { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.as_path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for &mut PathBuf { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.as_path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for File { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + // if self.is_terminal() { + // return Err(Error::IsTerminal); + // } + + let mut reader = BufReader::new(self); + loop { + let buffer = reader.fill_buf().await?; + let length = buffer.len(); + if length == 0 { + break; + } + buffer.hash_with(hash); + reader.consume(length); + } + Ok(()) + } +} + +// #[async_trait] +// impl AsyncChksumable for &File { +// async fn chksum_with(&mut self, hash: &mut H) -> Result<()> +// where +// H: Hash + Send, +// { +// // if self.is_terminal() { +// // return Err(Error::IsTerminal); +// // } + +// let mut reader = BufReader::new(self); +// loop { +// let buffer = reader.fill_buf().await?; +// let length = buffer.len(); +// if length == 0 { +// break; +// } +// buffer.hash_with(hash); +// reader.consume(length); +// } +// Ok(()) +// } +// } + +#[async_trait] +impl AsyncChksumable for &mut File { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + // if self.is_terminal() { + // return Err(Error::IsTerminal); + // } + + let mut reader = BufReader::new(self); + loop { + let buffer = reader.fill_buf().await?; + let length = buffer.len(); + if length == 0 { + break; + } + buffer.hash_with(hash); + reader.consume(length); + } + Ok(()) + } +} + +#[async_trait] +impl AsyncChksumable for DirEntry { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for &DirEntry { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for &mut DirEntry { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + self.path().chksum_with(hash).await + } +} + +#[async_trait] +impl AsyncChksumable for ReadDir { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let mut dir_entries = Vec::new(); + while let Some(dir_entry) = self.next_entry().await? { + dir_entries.push(dir_entry); + } + dir_entries.sort_by_key(DirEntry::path); + for mut dir_entry in dir_entries { + dir_entry.chksum_with(hash).await?; + } + Ok(()) + } +} + +#[async_trait] +impl AsyncChksumable for &mut ReadDir { + async fn chksum_with(&mut self, hash: &mut H) -> Result<()> + where + H: Hash + Send, + { + let mut dir_entries = Vec::new(); + while let Some(dir_entry) = self.next_entry().await? { + dir_entries.push(dir_entry); + } + dir_entries.sort_by_key(DirEntry::path); + for mut dir_entry in dir_entries { + dir_entry.chksum_with(hash).await?; + } + Ok(()) + } +}