Skip to content

Commit 01ec79a

Browse files
committed
Add async support with async-std and tokio runtimes
1 parent 9df4a46 commit 01ec79a

File tree

5 files changed

+325
-2
lines changed

5 files changed

+325
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added async support with async-std and tokio runtimes.
13+
1014
### Fixed
1115

1216
- Added missing method comments to improve documentation clarity.

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,14 @@ all-features = true
1616
rustdoc-args = ["--cfg", "docsrs"]
1717

1818
[dependencies]
19+
async-std = { version = "1.12.0", optional = true }
20+
async-trait = { version = "0.1.77", optional = true }
1921
chksum-hash-core = "0.0.0"
22+
futures-lite = { version = "2.2.0", optional = true }
2023
thiserror = "1.0.51"
24+
tokio = { version = "1.36.0", optional = true, features = ["fs", "io-util", "io-std"] }
25+
26+
[features]
27+
default = []
28+
async-runtime-async-std = ["async-std", "async-trait", "futures-lite"]
29+
async-runtime-tokio = ["async-trait", "async-std?/tokio1", "tokio"]

src/async_std.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#[cfg(not(feature = "async-runtime-tokio"))]
2+
use std::path::{Path, PathBuf};
3+
4+
use async_std::fs::{metadata, read_dir, DirEntry, File, ReadDir};
5+
use async_std::io::{BufReader, Stdin};
6+
use async_std::path::{Path as AsyncPath, PathBuf as AsyncPathBuf};
7+
use async_std::stream::StreamExt;
8+
use async_trait::async_trait;
9+
use futures_lite::io::AsyncBufReadExt;
10+
11+
use crate::{AsyncChksumable, Hash, Hashable, Result};
12+
13+
macro_rules! impl_async_chksumable {
14+
($($t:ty),+ => $i:tt) => {
15+
$(
16+
#[async_trait]
17+
impl AsyncChksumable for $t $i
18+
)*
19+
};
20+
}
21+
22+
impl_async_chksumable!(AsyncPath, &AsyncPath, &mut AsyncPath => {
23+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
24+
where
25+
H: Hash + Send,
26+
{
27+
let metadata = metadata(&self).await?;
28+
if metadata.is_dir() {
29+
read_dir(self).await?.chksum_with(hash).await
30+
} else {
31+
// everything treat as a file when it is not a directory
32+
File::open(self).await?.chksum_with(hash).await
33+
}
34+
}
35+
});
36+
37+
impl_async_chksumable!(AsyncPathBuf, &AsyncPathBuf, &mut AsyncPathBuf => {
38+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
39+
where
40+
H: Hash + Send,
41+
{
42+
self.as_path().chksum_with(hash).await
43+
}
44+
});
45+
46+
#[cfg(not(feature = "async-runtime-tokio"))]
47+
impl_async_chksumable!(Path, &Path, &mut Path => {
48+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
49+
where
50+
H: Hash + Send,
51+
{
52+
let metadata = metadata(&self).await?;
53+
if metadata.is_dir() {
54+
read_dir(self).await?.chksum_with(hash).await
55+
} else {
56+
// everything treat as a file when it is not a directory
57+
File::open(self).await?.chksum_with(hash).await
58+
}
59+
}
60+
});
61+
62+
63+
#[cfg(not(feature = "async-runtime-tokio"))]
64+
impl_async_chksumable!(PathBuf, &PathBuf, &mut PathBuf => {
65+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
66+
where
67+
H: Hash + Send,
68+
{
69+
self.as_path().chksum_with(hash).await
70+
}
71+
});
72+
73+
impl_async_chksumable!(File, &File, &mut File => {
74+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
75+
where
76+
H: Hash + Send,
77+
{
78+
// TODO: check async-rs/async-std#1073
79+
// if self.is_terminal() {
80+
// return Err(Error::IsTerminal);
81+
// }
82+
83+
let mut reader = BufReader::new(self);
84+
loop {
85+
let buffer = reader.fill_buf().await?;
86+
let length = buffer.len();
87+
if length == 0 {
88+
break;
89+
}
90+
buffer.hash_with(hash);
91+
reader.consume(length);
92+
}
93+
Ok(())
94+
}
95+
});
96+
97+
impl_async_chksumable!(DirEntry, &DirEntry, &mut DirEntry => {
98+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
99+
where
100+
H: Hash + Send,
101+
{
102+
self.path().chksum_with(hash).await
103+
}
104+
});
105+
106+
impl_async_chksumable!(ReadDir, &mut ReadDir => {
107+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
108+
where
109+
H: Hash + Send,
110+
{
111+
let mut dir_entries = Vec::new();
112+
while let Some(dir_entry) = self.next().await {
113+
dir_entries.push(dir_entry?);
114+
}
115+
dir_entries.sort_by_key(DirEntry::path);
116+
for mut dir_entry in dir_entries {
117+
AsyncChksumable::chksum_with(&mut dir_entry, hash).await?;
118+
}
119+
Ok(())
120+
}
121+
});
122+
123+
impl_async_chksumable!(Stdin, &mut Stdin => {
124+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
125+
where
126+
H: Hash + Send,
127+
{
128+
// TODO: check async-rs/async-std#1073
129+
// if self.is_terminal() {
130+
// return Err(Error::IsTerminal);
131+
// }
132+
133+
let mut reader = BufReader::new(self);
134+
loop {
135+
let buffer = reader.fill_buf().await?;
136+
let length = buffer.len();
137+
if length == 0 {
138+
break;
139+
}
140+
buffer.hash_with(hash);
141+
reader.consume(length);
142+
}
143+
Ok(())
144+
}
145+
});

src/lib.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,19 @@
3333
3434
#![forbid(unsafe_code)]
3535

36+
#[cfg(feature = "async-runtime-async-std")]
37+
mod async_std;
3638
mod error;
39+
#[cfg(feature = "async-runtime-tokio")]
40+
mod tokio;
3741

3842
use std::fmt::{Display, LowerHex, UpperHex};
3943
use std::fs::{read_dir, DirEntry, File, ReadDir};
4044
use std::io::{self, BufRead, BufReader, IsTerminal, Stdin, StdinLock};
4145
use std::path::{Path, PathBuf};
4246

47+
#[cfg(any(feature = "async-runtime-async-std", feature = "async-runtime-tokio"))]
48+
use async_trait::async_trait;
4349
#[doc(no_inline)]
4450
pub use chksum_hash_core as hash;
4551

@@ -70,6 +76,15 @@ where
7076
data.chksum::<T>()
7177
}
7278

79+
/// Computes the hash of the given input.
80+
#[cfg(any(feature = "async-runtime-async-std", feature = "async-runtime-tokio"))]
81+
pub async fn async_chksum<T>(mut data: impl AsyncChksumable + Send) -> Result<T::Digest>
82+
where
83+
T: Hash + Send,
84+
{
85+
data.chksum::<T>().await
86+
}
87+
7388
/// A trait for hash digests.
7489
pub trait Digest: Display {
7590
/// Returns a byte slice of the digest's contents.
@@ -235,7 +250,7 @@ impl_chksumable!(PathBuf, &PathBuf, &mut PathBuf => {
235250
where
236251
H: Hash,
237252
{
238-
self.as_path().chksum_with(hash)
253+
Chksumable::chksum_with(&mut self.as_path(), hash)
239254
}
240255
});
241256

@@ -267,7 +282,7 @@ impl_chksumable!(DirEntry, &DirEntry, &mut DirEntry => {
267282
where
268283
H: Hash,
269284
{
270-
self.path().chksum_with(hash)
285+
Chksumable::chksum_with(&mut self.path(), hash)
271286
}
272287
});
273288

@@ -316,3 +331,38 @@ impl_chksumable!(StdinLock<'_>, &mut StdinLock<'_> => {
316331
Ok(())
317332
}
318333
});
334+
335+
/// A trait for complex objects which must be processed chunk by chunk.
336+
#[cfg(any(feature = "async-runtime-async-std", feature = "async-runtime-tokio"))]
337+
#[async_trait]
338+
pub trait AsyncChksumable {
339+
/// Calculates the checksum of the object.
340+
async fn chksum<H>(&mut self) -> Result<H::Digest>
341+
where
342+
H: Hash + Send,
343+
{
344+
let mut hash = H::default();
345+
self.chksum_with(&mut hash).await?;
346+
Ok(hash.digest())
347+
}
348+
349+
/// Updates the given hash instance with the data from the object.
350+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
351+
where
352+
H: Hash + Send;
353+
}
354+
355+
#[cfg(any(feature = "async-runtime-async-std", feature = "async-runtime-tokio"))]
356+
#[async_trait]
357+
impl<T> AsyncChksumable for T
358+
where
359+
T: Hashable + Send,
360+
{
361+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
362+
where
363+
H: Hash + Send,
364+
{
365+
self.hash_with(hash);
366+
Ok(())
367+
}
368+
}

src/tokio.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use std::path::{Path, PathBuf};
2+
3+
use async_trait::async_trait;
4+
use tokio::fs::{metadata, read_dir, DirEntry, File, ReadDir};
5+
use tokio::io::{AsyncBufReadExt as _, BufReader, Stdin};
6+
7+
use crate::{AsyncChksumable, Hash, Hashable, Result};
8+
9+
macro_rules! impl_async_chksumable {
10+
($($t:ty),+ => $i:tt) => {
11+
$(
12+
#[async_trait]
13+
impl AsyncChksumable for $t $i
14+
)*
15+
};
16+
}
17+
18+
impl_async_chksumable!(Path, &Path, &mut Path => {
19+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
20+
where
21+
H: Hash + Send,
22+
{
23+
let metadata = metadata(&self).await?;
24+
if metadata.is_dir() {
25+
read_dir(self).await?.chksum_with(hash).await
26+
} else {
27+
// everything treat as a file when it is not a directory
28+
File::open(self).await?.chksum_with(hash).await
29+
}
30+
}
31+
32+
});
33+
34+
impl_async_chksumable!(PathBuf, &PathBuf, &mut PathBuf => {
35+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
36+
where
37+
H: Hash + Send,
38+
{
39+
self.as_path().chksum_with(hash).await
40+
}
41+
});
42+
43+
impl_async_chksumable!(File, &mut File => {
44+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
45+
where
46+
H: Hash + Send,
47+
{
48+
// TODO: check tokio-rs/tokio#6407
49+
// if self.is_terminal() {
50+
// return Err(Error::IsTerminal);
51+
// }
52+
53+
let mut reader = BufReader::new(self);
54+
loop {
55+
let buffer = reader.fill_buf().await?;
56+
let length = buffer.len();
57+
if length == 0 {
58+
break;
59+
}
60+
buffer.hash_with(hash);
61+
reader.consume(length);
62+
}
63+
Ok(())
64+
}
65+
});
66+
67+
impl_async_chksumable!(DirEntry, &DirEntry, &mut DirEntry => {
68+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
69+
where
70+
H: Hash + Send,
71+
{
72+
self.path().chksum_with(hash).await
73+
}
74+
});
75+
76+
impl_async_chksumable!(ReadDir, &mut ReadDir => {
77+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
78+
where
79+
H: Hash + Send,
80+
{
81+
let mut dir_entries = Vec::new();
82+
while let Some(dir_entry) = self.next_entry().await? {
83+
dir_entries.push(dir_entry);
84+
}
85+
dir_entries.sort_by_key(DirEntry::path);
86+
for mut dir_entry in dir_entries {
87+
dir_entry.chksum_with(hash).await?;
88+
}
89+
Ok(())
90+
}
91+
});
92+
93+
impl_async_chksumable!(Stdin, &mut Stdin => {
94+
async fn chksum_with<H>(&mut self, hash: &mut H) -> Result<()>
95+
where
96+
H: Hash + Send,
97+
{
98+
// TODO: check tokio-rs/tokio#6407
99+
// if self.is_terminal() {
100+
// return Err(Error::IsTerminal);
101+
// }
102+
103+
let mut reader = BufReader::new(self);
104+
loop {
105+
let buffer = reader.fill_buf().await?;
106+
let length = buffer.len();
107+
if length == 0 {
108+
break;
109+
}
110+
buffer.hash_with(hash);
111+
reader.consume(length);
112+
}
113+
Ok(())
114+
}
115+
});

0 commit comments

Comments
 (0)