diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac1c4f5..fd41882 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - rust: [1.70.0, stable] + rust: [1.81.0, stable] os: [ubuntu-latest, macOS-latest, windows-latest] steps: @@ -50,3 +50,7 @@ jobs: run: cargo check --no-default-features --features tokio-runtime,link_to - name: Run unit tests (Tokio) run: cargo test --verbose --no-default-features --features tokio-runtime,link_to --lib + - name: Check (Smol) + run: cargo check --no-default-features --features smol-runtime,link_to + - name: Run unit tests (Smol) + run: cargo test --verbose --no-default-features --features smol-runtime,link_to --lib diff --git a/Cargo.toml b/Cargo.toml index f77daae..54cf959 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ either = "1.6.1" futures = { version = "0.3.17", optional = true } hex = "0.4.3" memmap2 = { version = "0.5.8", optional = true } +smol = { version = "2.0.0", optional = true } miette = "5.7.0" reflink-copy = "0.1.9" serde = "1.0.130" @@ -43,6 +44,8 @@ libc = { version = "0.2.144", optional = true } async-attributes = { version = "1.1.2" } criterion = "0.4.0" lazy_static = "1.4.0" +smol-macros = "0.1.1" +macro_rules_attribute = "0.2.0" tokio = { version = "1.12.0", features = [ "fs", "io-util", @@ -61,3 +64,4 @@ mmap = ["memmap2", "libc"] async-std = ["dep:async-std", "futures"] link_to = [] tokio-runtime = ["tokio", "tokio-stream", "futures"] +smol-runtime = ["smol", "futures"] diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index a0405f8..9356e72 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,5 +1,7 @@ #[cfg(feature = "async-std")] use async_std::fs as afs; +#[cfg(feature = "smol")] +use smol::fs as afs; #[cfg(feature = "link_to")] use std::path::PathBuf; #[cfg(all(test, feature = "tokio"))] @@ -22,6 +24,15 @@ where TOKIO_RUNTIME.block_on(future) } +#[cfg(all(test, feature = "smol"))] +#[inline] +pub fn block_on(future: F) -> T +where + F: std::future::Future, +{ + smol::block_on(future) +} + use std::fs::{self, File}; use std::io::prelude::*; @@ -62,7 +73,7 @@ fn baseline_read_many_sync(c: &mut Criterion) { }); } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] fn baseline_read_async(c: &mut Criterion) { let tmp = tempfile::tempdir().unwrap(); let path = tmp.path().join("test_file"); @@ -75,7 +86,7 @@ fn baseline_read_async(c: &mut Criterion) { }); } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] fn baseline_read_many_async(c: &mut Criterion) { let tmp = tempfile::tempdir().unwrap(); let paths: Vec<_> = (0..) @@ -190,7 +201,7 @@ fn read_hash_sync_big_data_xxh3(c: &mut Criterion) { }); } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] fn read_hash_many_async(c: &mut Criterion) { let tmp = tempfile::tempdir().unwrap(); let cache = tmp.path().to_owned(); @@ -212,7 +223,7 @@ fn read_hash_many_async(c: &mut Criterion) { }); } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] fn read_hash_async(c: &mut Criterion) { let tmp = tempfile::tempdir().unwrap(); let cache = tmp.path().to_owned(); @@ -223,7 +234,7 @@ fn read_hash_async(c: &mut Criterion) { }); } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] fn read_async(c: &mut Criterion) { let tmp = tempfile::tempdir().unwrap(); let cache = tmp.path().to_owned(); @@ -234,7 +245,7 @@ fn read_async(c: &mut Criterion) { }); } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] fn read_hash_async_big_data(c: &mut Criterion) { let tmp = tempfile::tempdir().unwrap(); let cache = tmp.path().to_owned(); @@ -278,7 +289,7 @@ fn write_hash_xxh3(c: &mut Criterion) { }); } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] fn write_hash_async(c: &mut Criterion) { let tmp = tempfile::tempdir().unwrap(); let cache = tmp.path().to_owned(); @@ -293,7 +304,7 @@ fn write_hash_async(c: &mut Criterion) { }); } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] fn write_hash_async_xxh3(c: &mut Criterion) { let tmp = tempfile::tempdir().unwrap(); let cache = tmp.path().to_owned(); @@ -325,7 +336,7 @@ fn create_tmpfile(tmp: &tempfile::TempDir, buf: &[u8]) -> PathBuf { } #[cfg(feature = "link_to")] -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] fn link_to_async(c: &mut Criterion) { let tmp = tempfile::tempdir().unwrap(); let target = create_tmpfile(&tmp, b"hello world"); @@ -348,7 +359,10 @@ fn link_to_async(c: &mut Criterion) { }); } -#[cfg(all(feature = "link_to", any(feature = "async-std", feature = "tokio")))] +#[cfg(all( + feature = "link_to", + any(feature = "async-std", feature = "tokio", feature = "smol") +))] fn link_to_hash_async(c: &mut Criterion) { let tmp = tempfile::tempdir().unwrap(); let target = create_tmpfile(&tmp, b"hello world"); @@ -405,7 +419,7 @@ criterion_group!( read_hash_sync_big_data_xxh3, ); -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] criterion_group!( benches_async, baseline_read_async, @@ -418,7 +432,10 @@ criterion_group!( read_hash_async_big_data, ); -#[cfg(all(feature = "link_to", any(feature = "async-std", feature = "tokio")))] +#[cfg(all( + feature = "link_to", + any(feature = "async-std", feature = "tokio", feature = "smol") +))] criterion_group!(link_to_benches_async, link_to_async, link_to_hash_async,); #[cfg(feature = "link_to")] @@ -426,15 +443,18 @@ criterion_group!(link_to_benches, link_to_sync, link_to_hash_sync); #[cfg(all( feature = "link_to", - not(any(feature = "async-std", feature = "tokio")) + not(any(feature = "async-std", feature = "tokio", feature = "smol")) ))] criterion_main!(benches, link_to_benches); #[cfg(all( not(feature = "link_to"), - any(feature = "async-std", feature = "tokio") + any(feature = "async-std", feature = "tokio", feature = "smol") ))] criterion_main!(benches, benches_async); -#[cfg(all(feature = "link_to", any(feature = "async-std", feature = "tokio")))] +#[cfg(all( + feature = "link_to", + any(feature = "async-std", feature = "tokio", feature = "smol") +))] criterion_main!( benches, benches_async, @@ -443,6 +463,6 @@ criterion_main!( ); #[cfg(all( not(feature = "link_to"), - not(any(feature = "async-std", feature = "tokio")) + not(any(feature = "async-std", feature = "tokio", feature = "smol")) ))] criterion_main!(benches); diff --git a/justfile b/justfile index fb183c1..0a61415 100644 --- a/justfile +++ b/justfile @@ -2,12 +2,14 @@ @help: just -l -# Run tests on both runtimes with cargo nextest +# Run tests on all runtimes with cargo nextest @test: echo "----------\nasync-std:\n" cargo nextest run echo "\n----------\ntokio:\n" cargo nextest run --no-default-features --features tokio-runtime + echo "\n----------\nsmol:\n" + cargo nextest run --no-default-features --features smol-runtime # Run benchmarks with `cargo bench` @bench: @@ -15,6 +17,8 @@ cargo bench echo "\n----------\ntokio:\n" cargo bench --no-default-features --features tokio-runtime + echo "\n----------\nsmol:\n" + cargo bench --no-default-features --features smol-runtime # Run benchmarks with `cargo criterion` @criterion: @@ -22,6 +26,8 @@ cargo criterion echo "\n----------\ntokio:\n" cargo criterion --no-default-features --features tokio-runtime + echo "\n----------\nsmol:\n" + cargo criterion --no-default-features --features smol-runtime # Generate a changelog with git-cliff changelog TAG: diff --git a/src/async_lib.rs b/src/async_lib.rs index 5af05bb..43ac2bd 100644 --- a/src/async_lib.rs +++ b/src/async_lib.rs @@ -1,75 +1,105 @@ #[cfg(feature = "async-std")] pub use async_std::fs::File; +#[cfg(feature = "smol")] +pub use smol::fs::File; #[cfg(feature = "tokio")] pub use tokio::fs::File; #[cfg(feature = "async-std")] pub use futures::io::AsyncRead; +#[cfg(feature = "smol")] +pub use futures::io::AsyncRead; #[cfg(feature = "tokio")] pub use tokio::io::AsyncRead; #[cfg(feature = "async-std")] pub use futures::io::AsyncReadExt; +#[cfg(feature = "smol")] +pub use futures::io::AsyncReadExt; #[cfg(feature = "tokio")] pub use tokio::io::AsyncReadExt; #[cfg(feature = "async-std")] pub use futures::io::AsyncBufReadExt; +#[cfg(feature = "smol")] +pub use futures::io::AsyncBufReadExt; #[cfg(feature = "tokio")] pub use tokio::io::AsyncBufReadExt; #[cfg(feature = "async-std")] pub use futures::io::AsyncWrite; +#[cfg(feature = "smol")] +pub use futures::io::AsyncWrite; #[cfg(feature = "tokio")] pub use tokio::io::AsyncWrite; #[cfg(feature = "async-std")] pub use futures::io::AsyncWriteExt; +#[cfg(feature = "smol")] +pub use futures::io::AsyncWriteExt; #[cfg(feature = "tokio")] pub use tokio::io::AsyncWriteExt; #[cfg(feature = "async-std")] pub use async_std::fs::read; +#[cfg(feature = "smol")] +pub use smol::fs::read; #[cfg(feature = "tokio")] pub use tokio::fs::read; #[cfg(feature = "async-std")] pub use async_std::fs::copy; +#[cfg(feature = "smol")] +pub use smol::fs::copy; #[cfg(feature = "tokio")] pub use tokio::fs::copy; #[cfg(feature = "async-std")] pub use async_std::fs::metadata; +#[cfg(feature = "smol")] +pub use smol::fs::metadata; #[cfg(feature = "tokio")] pub use tokio::fs::metadata; #[cfg(feature = "async-std")] pub use async_std::fs::remove_file; +#[cfg(feature = "smol")] +pub use smol::fs::remove_file; #[cfg(feature = "tokio")] pub use tokio::fs::remove_file; #[cfg(feature = "async-std")] pub use async_std::fs::create_dir_all; +#[cfg(feature = "smol")] +pub use smol::fs::create_dir_all; #[cfg(feature = "tokio")] pub use tokio::fs::create_dir_all; #[cfg(feature = "async-std")] pub use async_std::fs::remove_dir_all; +#[cfg(feature = "smol")] +pub use smol::fs::remove_dir_all; #[cfg(feature = "tokio")] pub use tokio::fs::remove_dir_all; #[cfg(feature = "async-std")] pub use async_std::fs::DirBuilder; +#[cfg(feature = "smol")] +pub use smol::fs::DirBuilder; #[cfg(feature = "tokio")] pub use tokio::fs::DirBuilder; #[cfg(feature = "async-std")] pub use async_std::fs::OpenOptions; +#[cfg(feature = "smol")] +pub use smol::fs::OpenOptions; #[cfg(feature = "tokio")] pub use tokio::fs::OpenOptions; #[cfg(feature = "async-std")] pub use async_std::io::BufReader; +#[cfg(feature = "smol")] +pub use futures::io::BufReader; #[cfg(feature = "tokio")] pub use tokio::io::BufReader; @@ -83,9 +113,16 @@ pub fn lines_to_stream(lines: futures::io::Lines) -> futures::io::Lines pub fn lines_to_stream(lines: tokio::io::Lines) -> tokio_stream::wrappers::LinesStream { tokio_stream::wrappers::LinesStream::new(lines) } +#[cfg(feature = "smol")] +#[inline] +pub fn lines_to_stream(lines: futures::io::Lines) -> futures::io::Lines { + lines +} #[cfg(feature = "async-std")] pub use async_std::task::spawn_blocking; +#[cfg(feature = "smol")] +pub use smol::unblock as spawn_blocking; #[cfg(feature = "tokio")] pub use tokio::task::spawn_blocking; @@ -103,6 +140,13 @@ pub use tokio::task::JoinHandle; pub fn unwrap_joinhandle_value(value: T) -> T { value } +#[cfg(feature = "smol")] +pub use smol::Task as JoinHandle; +#[cfg(feature = "smol")] +#[inline] +pub fn unwrap_joinhandle_value(value: T) -> T { + value +} use tempfile::NamedTempFile; @@ -135,3 +179,17 @@ pub async fn create_named_tempfile( _ => None, } } + +#[cfg(feature = "smol")] +#[inline] +pub async fn create_named_tempfile( + tmp_path: std::path::PathBuf, +) -> Option> { + let cloned = tmp_path.clone(); + + Some( + spawn_blocking(|| NamedTempFile::new_in(tmp_path)) + .await + .with_context(|| format!("Failed to create a temp file at {}", cloned.display())), + ) +} diff --git a/src/content/linkto.rs b/src/content/linkto.rs index 26e18b7..b0cdff1 100644 --- a/src/content/linkto.rs +++ b/src/content/linkto.rs @@ -2,12 +2,12 @@ use ssri::{Algorithm, Integrity, IntegrityOpts}; use std::fs::DirBuilder; use std::fs::File; use std::path::{Path, PathBuf}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::pin::Pin; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::task::{Context, Poll}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::AsyncRead; use crate::content::path; use crate::errors::{IoErrorExt, Result}; @@ -106,7 +106,7 @@ impl std::io::Read for ToLinker { /// An `AsyncRead`-like type that calculates the integrity of a file as it is /// read. When the linker is committed, a symlink is created from the cache to /// the target file using the integrity computed from the file's contents. -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub struct AsyncToLinker { /// The path to the target file that will be symlinked from the cache. target: PathBuf, @@ -118,7 +118,7 @@ pub struct AsyncToLinker { builder: IntegrityOpts, } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl AsyncRead for AsyncToLinker { #[cfg(feature = "async-std")] fn poll_read( @@ -146,9 +146,22 @@ impl AsyncRead for AsyncToLinker { } Poll::Ready(Ok(())) } + + #[cfg(feature = "smol")] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let amt = futures::ready!(Pin::new(&mut self.fd).poll_read(cx, buf))?; + if amt > 0 { + self.builder.input(&buf[..amt]); + } + Poll::Ready(Ok(amt)) + } } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl AsyncToLinker { pub async fn new(cache: &Path, algo: Algorithm, target: &Path) -> Result { let file = crate::async_lib::File::open(target) @@ -176,11 +189,17 @@ mod tests { #[cfg(feature = "async-std")] use async_attributes::test as async_test; + #[cfg(feature = "smol")] + use macro_rules_attribute::apply; + #[cfg(feature = "smol")] + use smol_macros::test; #[cfg(feature = "tokio")] use tokio::test as async_test; #[cfg(feature = "async-std")] use futures::io::AsyncReadExt; + #[cfg(feature = "smol")] + use futures::io::AsyncReadExt; #[cfg(feature = "tokio")] use tokio::io::AsyncReadExt; @@ -254,4 +273,37 @@ mod tests { assert!(file_type.is_symlink()); assert_eq!(std::fs::read(cpath).unwrap(), b"hello world"); } + + #[cfg(feature = "smol")] + #[apply(test!)] + async fn basic_async_link() { + let tmp = tempfile::tempdir().unwrap(); + let target = create_tmpfile(&tmp, b"hello world"); + + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let mut linker = AsyncToLinker::new(&dir, Algorithm::Sha256, &target) + .await + .unwrap(); + + // read all of the data from the linker, which will calculate the integrity + // hash. + let mut buf: Vec = Vec::new(); + AsyncReadExt::read_to_end(&mut linker, &mut buf) + .await + .unwrap(); + assert_eq!(buf, b"hello world"); + + // commit the linker, creating a symlink in the cache and an integrity + // hash. + let sri = linker.commit().await.unwrap(); + assert_eq!(sri.to_string(), Integrity::from(b"hello world").to_string()); + + let cpath = path::content_path(&dir, &sri); + assert!(cpath.exists()); + let metadata = std::fs::symlink_metadata(&cpath).unwrap(); + let file_type = metadata.file_type(); + assert!(file_type.is_symlink()); + assert_eq!(std::fs::read(cpath).unwrap(), b"hello world"); + } } diff --git a/src/content/read.rs b/src/content/read.rs index 39efbdb..460fa58 100644 --- a/src/content/read.rs +++ b/src/content/read.rs @@ -1,17 +1,17 @@ use std::fs::{self, File}; use std::io::Read; use std::path::Path; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::pin::Pin; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::task::{Context, Poll}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::AsyncReadExt; use ssri::{Algorithm, Integrity, IntegrityChecker}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::AsyncRead; use crate::content::path; use crate::errors::{IoErrorExt, Result}; @@ -35,13 +35,13 @@ impl Reader { } } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub struct AsyncReader { fd: crate::async_lib::File, checker: IntegrityChecker, } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl AsyncRead for AsyncReader { #[cfg(feature = "async-std")] fn poll_read( @@ -69,9 +69,20 @@ impl AsyncRead for AsyncReader { self.checker.input(&buf.filled()[pre_len..]); Poll::Ready(Ok(())) } + + #[cfg(feature = "smol")] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let amt = futures::ready!(Pin::new(&mut self.fd).poll_read(cx, buf))?; + self.checker.input(&buf[..amt]); + Poll::Ready(Ok(amt)) + } } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl AsyncReader { pub fn check(self) -> Result { Ok(self.checker.result()?) @@ -91,7 +102,7 @@ pub fn open(cache: &Path, sri: Integrity) -> Result { }) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn open_async(cache: &Path, sri: Integrity) -> Result { let cpath = path::content_path(cache, &sri); Ok(AsyncReader { @@ -117,7 +128,7 @@ pub fn read(cache: &Path, sri: &Integrity) -> Result> { Ok(ret) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn read_async<'a>(cache: &'a Path, sri: &'a Integrity) -> Result> { let cpath = path::content_path(cache, sri); let ret = crate::async_lib::read(&cpath).await.with_context(|| { @@ -160,7 +171,7 @@ pub fn reflink(cache: &Path, sri: &Integrity, to: &Path) -> Result<()> { reflink_unchecked(cache, sri, to) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn reflink_async(cache: &Path, sri: &Integrity, to: &Path) -> Result<()> { let mut reader = open_async(cache, sri.clone()).await?; let mut buf = [0u8; 1024 * 8]; @@ -214,7 +225,7 @@ pub fn copy(cache: &Path, sri: &Integrity, to: &Path) -> Result { Ok(size as u64) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn copy_unchecked_async<'a>( cache: &'a Path, sri: &'a Integrity, @@ -230,7 +241,7 @@ pub async fn copy_unchecked_async<'a>( }) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn copy_async<'a>(cache: &'a Path, sri: &'a Integrity, to: &'a Path) -> Result { let mut reader = open_async(cache, sri.clone()).await?; let mut buf: [u8; 1024] = [0; 1024]; @@ -285,7 +296,7 @@ pub fn hard_link(cache: &Path, sri: &Integrity, to: &Path) -> Result<()> { Ok(()) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn hard_link_async(cache: &Path, sri: &Integrity, to: &Path) -> Result<()> { let mut reader = open_async(cache, sri.clone()).await?; let mut buf = [0u8; 1024 * 8]; @@ -315,7 +326,7 @@ pub fn has_content(cache: &Path, sri: &Integrity) -> Option { } } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn has_content_async(cache: &Path, sri: &Integrity) -> Option { if crate::async_lib::metadata(path::content_path(cache, sri)) .await diff --git a/src/content/rm.rs b/src/content/rm.rs index 2167c70..f813651 100644 --- a/src/content/rm.rs +++ b/src/content/rm.rs @@ -16,7 +16,7 @@ pub fn rm(cache: &Path, sri: &Integrity) -> Result<()> { Ok(()) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn rm_async(cache: &Path, sri: &Integrity) -> Result<()> { crate::async_lib::remove_file(path::content_path(cache, sri)) .await diff --git a/src/content/write.rs b/src/content/write.rs index 8b7961e..4bdc7a6 100644 --- a/src/content/write.rs +++ b/src/content/write.rs @@ -1,21 +1,21 @@ use std::fs::DirBuilder; use std::io::prelude::*; use std::path::{Path, PathBuf}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::pin::Pin; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::sync::Mutex; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::task::{Context, Poll}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use futures::prelude::*; #[cfg(feature = "mmap")] use memmap2::MmapMut; use ssri::{Algorithm, Integrity, IntegrityOpts}; use tempfile::NamedTempFile; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::{AsyncWrite, JoinHandle}; use crate::content::path; use crate::errors::{IoErrorExt, Result}; @@ -129,16 +129,16 @@ impl Write for Writer { } } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub struct AsyncWriter(Mutex); -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] enum State { Idle(Option), Busy(JoinHandle), } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] struct Inner { cache: PathBuf, builder: IntegrityOpts, @@ -148,13 +148,13 @@ struct Inner { last_op: Option, } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] enum Operation { Write(std::io::Result), Flush(std::io::Result<()>), } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl AsyncWriter { #[allow(clippy::new_ret_no_self)] #[allow(clippy::needless_lifetimes)] @@ -187,7 +187,7 @@ impl AsyncWriter { }))))) } _ => Err(Error::IoError( - std::io::Error::new(std::io::ErrorKind::Other, "temp file create error"), + std::io::Error::other("temp file create error"), "Possible memory issues for file handle".into(), )), } @@ -275,7 +275,7 @@ impl AsyncWriter { } } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl AsyncWrite for AsyncWriter { fn poll_write( self: Pin<&mut Self>, @@ -407,6 +407,11 @@ impl AsyncWrite for AsyncWriter { fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.poll_close_impl(cx) } + + #[cfg(feature = "smol")] + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_close_impl(cx) + } } #[cfg(feature = "tokio")] @@ -431,7 +436,7 @@ fn update_state(current_state: &mut State, next_state: State) { *current_state = next_state; } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl AsyncWriter { #[inline] fn poll_close_impl( @@ -491,20 +496,18 @@ fn make_mmap(tmpfile: &mut NamedTempFile, size: Option) -> Result std::io::Result<()> { - use std::io::{Error, ErrorKind}; + use std::io::Error; use std::os::fd::AsRawFd; let fd = file.as_raw_fd(); match unsafe { libc::posix_fallocate64(fd, 0, size as i64) } { 0 => Ok(()), - libc::ENOSPC => Err(Error::new( - ErrorKind::Other, // ErrorKind::StorageFull is unstable + libc::ENOSPC => Err(Error::other( "cannot allocate file: no space left on device", )), - err => Err(Error::new( - ErrorKind::Other, - format!("posix_fallocate64 failed with code {err}"), - )), + err => Err(Error::other(format!( + "posix_fallocate64 failed with code {err}" + ))), } } @@ -522,12 +525,16 @@ fn make_mmap(_: &mut NamedTempFile, _: Option) -> Result> #[cfg(test)] mod tests { use super::*; - #[cfg(any(feature = "async-std", feature = "tokio"))] + #[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::AsyncWriteExt; use tempfile; #[cfg(feature = "async-std")] use async_attributes::test as async_test; + #[cfg(feature = "smol")] + use macro_rules_attribute::apply; + #[cfg(feature = "smol")] + use smol_macros::test; #[cfg(feature = "tokio")] use tokio::test as async_test; @@ -561,4 +568,21 @@ mod tests { b"hello world" ); } + + #[cfg(feature = "smol")] + #[apply(test!)] + async fn basic_async_write() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let mut writer = AsyncWriter::new(&dir, Algorithm::Sha256, None) + .await + .unwrap(); + writer.write_all(b"hello world").await.unwrap(); + let sri = writer.close().await.unwrap(); + assert_eq!(sri.to_string(), Integrity::from(b"hello world").to_string()); + assert_eq!( + std::fs::read(path::content_path(&dir, &sri)).unwrap(), + b"hello world" + ); + } } diff --git a/src/errors.rs b/src/errors.rs index ccb864d..3cef1ed 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -59,5 +59,5 @@ impl IoErrorExt for std::result::Result { } pub fn io_error(err: impl Into>) -> std::io::Error { - std::io::Error::new(std::io::ErrorKind::Other, err) + std::io::Error::other(err) } diff --git a/src/get.rs b/src/get.rs index b4f8101..4a032bf 100644 --- a/src/get.rs +++ b/src/get.rs @@ -1,13 +1,13 @@ //! Functions for reading from cache. use std::path::Path; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::pin::Pin; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::task::{Context as TaskContext, Poll}; use ssri::{Algorithm, Integrity}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::AsyncRead; use crate::content::read; use crate::errors::{Error, Result}; @@ -21,12 +21,12 @@ use crate::index::{self, Metadata}; /// /// Make sure to call `.check()` when done reading to verify that the /// extracted data passes integrity verification. -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub struct Reader { reader: read::AsyncReader, } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl AsyncRead for Reader { #[cfg(feature = "async-std")] fn poll_read( @@ -45,9 +45,18 @@ impl AsyncRead for Reader { ) -> Poll> { Pin::new(&mut self.reader).poll_read(cx, buf) } + + #[cfg(feature = "smol")] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut [u8], + ) -> Poll> { + Pin::new(&mut self.reader).poll_read(cx, buf) + } } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl Reader { /// Checks that data read from disk passes integrity checks. Returns the /// algorithm that was used verified the data. Should be called only after @@ -151,7 +160,7 @@ impl Reader { /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn read(cache: P, key: K) -> Result> where P: AsRef, @@ -182,7 +191,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn read_hash

(cache: P, sri: &Integrity) -> Result> where P: AsRef, @@ -204,7 +213,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn copy(cache: P, key: K, to: Q) -> Result where P: AsRef, @@ -235,7 +244,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn copy_unchecked(cache: P, key: K, to: Q) -> Result where P: AsRef, @@ -267,7 +276,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn copy_hash(cache: P, sri: &Integrity, to: Q) -> Result where P: AsRef, @@ -291,7 +300,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn copy_hash_unchecked(cache: P, sri: &Integrity, to: Q) -> Result where P: AsRef, @@ -319,7 +328,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn reflink(cache: P, key: K, to: Q) -> Result<()> where P: AsRef, @@ -356,7 +365,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn reflink_unchecked(cache: P, key: K, to: Q) -> Result<()> where P: AsRef, @@ -393,7 +402,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn reflink_hash(cache: P, sri: &Integrity, to: Q) -> Result<()> where P: AsRef, @@ -403,7 +412,7 @@ where } /// Hard links a cache entry by hash to a specified location. -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn hard_link_hash(cache: P, sri: &Integrity, to: Q) -> Result<()> where P: AsRef, @@ -413,7 +422,7 @@ where } /// Hard links a cache entry by key to a specified location. -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn hard_link(cache: P, key: K, to: Q) -> Result<()> where P: AsRef, @@ -435,7 +444,7 @@ where /// Note that the existence of a metadata entry is not a guarantee that the /// underlying data exists, since they are stored and managed independently. /// To verify that the underlying associated data exists, use `exists()`. -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn metadata(cache: P, key: K) -> Result> where P: AsRef, @@ -445,7 +454,7 @@ where } /// Returns true if the given hash exists in the cache. -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn exists>(cache: P, sri: &Integrity) -> bool { read::has_content_async(cache.as_ref(), sri).await.is_some() } @@ -912,12 +921,16 @@ pub fn exists_sync>(cache: P, sri: &Integrity) -> bool { #[cfg(test)] mod tests { - #[cfg(any(feature = "async-std", feature = "tokio"))] + #[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::AsyncReadExt; use std::fs; #[cfg(feature = "async-std")] use async_attributes::test as async_test; + #[cfg(feature = "smol")] + use macro_rules_attribute::apply; + #[cfg(feature = "smol")] + use smol_macros::test; #[cfg(feature = "tokio")] use tokio::test as async_test; @@ -935,6 +948,20 @@ mod tests { assert_eq!(str, String::from("hello world")); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_open() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + crate::write(&dir, "my-key", b"hello world").await.unwrap(); + + let mut handle = crate::Reader::open(&dir, "my-key").await.unwrap(); + let mut str = String::new(); + handle.read_to_string(&mut str).await.unwrap(); + handle.check().unwrap(); + assert_eq!(str, String::from("hello world")); + } + #[cfg(any(feature = "async-std", feature = "tokio"))] #[async_test] async fn test_open_hash() { @@ -949,6 +976,20 @@ mod tests { assert_eq!(str, String::from("hello world")); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_open_hash() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let sri = crate::write(&dir, "my-key", b"hello world").await.unwrap(); + + let mut handle = crate::Reader::open_hash(&dir, sri).await.unwrap(); + let mut str = String::new(); + handle.read_to_string(&mut str).await.unwrap(); + handle.check().unwrap(); + assert_eq!(str, String::from("hello world")); + } + #[test] fn test_open_sync() { use std::io::prelude::*; @@ -988,6 +1029,17 @@ mod tests { assert_eq!(data, b"hello world"); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_read() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + crate::write(&dir, "my-key", b"hello world").await.unwrap(); + + let data = crate::read(&dir, "my-key").await.unwrap(); + assert_eq!(data, b"hello world"); + } + #[cfg(any(feature = "async-std", feature = "tokio"))] #[async_test] async fn test_read_hash() { @@ -999,6 +1051,17 @@ mod tests { assert_eq!(data, b"hello world"); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_read_hash() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let sri = crate::write(&dir, "my-key", b"hello world").await.unwrap(); + + let data = crate::read_hash(&dir, &sri).await.unwrap(); + assert_eq!(data, b"hello world"); + } + #[test] fn test_read_sync() { let tmp = tempfile::tempdir().unwrap(); @@ -1032,6 +1095,19 @@ mod tests { assert_eq!(data, b"hello world"); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_copy() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path(); + let dest = dir.join("data"); + crate::write(&dir, "my-key", b"hello world").await.unwrap(); + + crate::copy(&dir, "my-key", &dest).await.unwrap(); + let data = crate::async_lib::read(&dest).await.unwrap(); + assert_eq!(data, b"hello world"); + } + #[cfg(any(feature = "async-std", feature = "tokio"))] #[async_test] async fn test_copy_hash() { @@ -1045,6 +1121,19 @@ mod tests { assert_eq!(data, b"hello world"); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_copy_hash() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path(); + let dest = dir.join("data"); + let sri = crate::write(&dir, "my-key", b"hello world").await.unwrap(); + + crate::copy_hash(&dir, &sri, &dest).await.unwrap(); + let data = crate::async_lib::read(&dest).await.unwrap(); + assert_eq!(data, b"hello world"); + } + #[test] fn test_copy_sync() { let tmp = tempfile::tempdir().unwrap(); diff --git a/src/index.rs b/src/index.rs index 9ecb961..30fe240 100644 --- a/src/index.rs +++ b/src/index.rs @@ -9,7 +9,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use digest::Digest; use either::{Left, Right}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use futures::stream::StreamExt; use serde_derive::{Deserialize, Serialize}; use serde_json::Value; @@ -18,7 +18,7 @@ use sha2::Sha256; use ssri::Integrity; use walkdir::WalkDir; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::{AsyncBufReadExt, AsyncWriteExt}; use crate::content::path::content_path; use crate::errors::{IoErrorExt, Result}; @@ -103,7 +103,7 @@ pub fn insert(cache: &Path, key: &str, opts: WriteOpts) -> Result { .unwrap()) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] /// Asynchronous raw insertion into the cache index. pub async fn insert_async<'a>(cache: &'a Path, key: &'a str, opts: WriteOpts) -> Result { let bucket = bucket_path(cache, key); @@ -175,7 +175,7 @@ pub fn find(cache: &Path, key: &str) -> Result> { })) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] /// Asynchronous raw index Metadata access. pub async fn find_async(cache: &Path, key: &str) -> Result> { let bucket = bucket_path(cache, key); @@ -224,7 +224,7 @@ pub fn delete(cache: &Path, key: &str) -> Result<()> { .map(|_| ()) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] /// Asynchronously deletes an index entry, without deleting the actual cache /// data entry. pub async fn delete_async(cache: &Path, key: &str) -> Result<()> { @@ -351,7 +351,7 @@ fn bucket_entries(bucket: &Path) -> std::io::Result> { }) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] async fn bucket_entries_async(bucket: &Path) -> std::io::Result> { let file_result = crate::async_lib::File::open(bucket).await; let file = if let Err(err) = file_result { @@ -424,7 +424,7 @@ impl RemoveOpts { /// Removes an individual index metadata entry. /// If remove_fully is set to false (default), the associated content will be left in the cache. /// If remove_fully is true, both the index entry and the contents will be physically removed from the disk - #[cfg(any(feature = "async-std", feature = "tokio"))] + #[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn remove(self, cache: P, key: K) -> Result<()> where P: AsRef, @@ -454,6 +454,10 @@ mod tests { #[cfg(feature = "async-std")] use async_attributes::test as async_test; + #[cfg(feature = "smol")] + use macro_rules_attribute::apply; + #[cfg(feature = "smol")] + use smol_macros::test; #[cfg(feature = "tokio")] use tokio::test as async_test; @@ -495,6 +499,21 @@ mod tests { assert_eq!(entry, MOCK_ENTRY); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn insert_async_basic() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let sri: Integrity = "sha1-deadbeef".parse().unwrap(); + let time = 1_234_567; + let opts = WriteOpts::new().integrity(sri).time(time); + futures::executor::block_on(async { + insert_async(&dir, "hello", opts).await.unwrap(); + }); + let entry = std::fs::read_to_string(bucket_path(&dir, "hello")).unwrap(); + assert_eq!(entry, MOCK_ENTRY); + } + #[test] fn find_basic() { let tmp = tempfile::tempdir().unwrap(); @@ -552,6 +571,21 @@ mod tests { assert_eq!(find(&dir, "hello").unwrap(), None); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn delete_async_basic() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let sri: Integrity = "sha1-deadbeef".parse().unwrap(); + let time = 1_234_567; + let opts = WriteOpts::new().integrity(sri).time(time); + insert(&dir, "hello", opts).unwrap(); + futures::executor::block_on(async { + delete_async(&dir, "hello").await.unwrap(); + }); + assert_eq!(find(&dir, "hello").unwrap(), None); + } + #[test] fn delete_fully() { let tmp = tempfile::tempdir().unwrap(); @@ -590,6 +624,26 @@ mod tests { assert!(!content.exists()); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn delete_fully_async() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let content = content_path(&dir, &"sha1-deadbeef".parse().unwrap()); + fs::create_dir_all(content.parent().unwrap()).unwrap(); + fs::write(content.as_path(), "hello").unwrap(); + let sri: Integrity = "sha1-deadbeef".parse().unwrap(); + let time = 1_234_567; + insert(&dir, "hello", WriteOpts::new().integrity(sri).time(time)).unwrap(); + RemoveOpts::new() + .remove_fully(true) + .remove(&dir, "hello") + .await + .unwrap(); + assert_eq!(find(&dir, "hello").unwrap(), None); + assert!(!content.exists()); + } + #[test] fn round_trip() { let tmp = tempfile::tempdir().unwrap(); @@ -639,6 +693,33 @@ mod tests { ); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn round_trip_async() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let sri: Integrity = "sha1-deadbeef".parse().unwrap(); + let time = 1_234_567; + let opts = WriteOpts::new().integrity(sri.clone()).time(time); + futures::executor::block_on(async { + insert_async(&dir, "hello", opts).await.unwrap(); + }); + let entry = futures::executor::block_on(async { + find_async(&dir, "hello").await.unwrap().unwrap() + }); + assert_eq!( + entry, + Metadata { + key: String::from("hello"), + integrity: sri, + time, + size: 0, + metadata: json!(null), + raw_metadata: None, + } + ); + } + #[test] fn ls_basic() { let tmp = tempfile::tempdir().unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 07f0826..7884a6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,10 +156,20 @@ #[cfg(all(feature = "async-std", feature = "tokio-runtime"))] compile_error!("Only either feature \"async-std\" or \"tokio-runtime\" must be enabled for this crate, not both."); +#[cfg(all(feature = "async-std", feature = "smol"))] +compile_error!( + "Only either feature \"async-std\" or \"smol\" must be enabled for this crate, not both." +); + +#[cfg(all(feature = "tokio-runtime", feature = "smol"))] +compile_error!( + "Only either feature \"tokio-runtime\" or \"smol\" must be enabled for this crate, not both." +); + pub use serde_json::Value; pub use ssri::{Algorithm, Integrity}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] mod async_lib; mod content; diff --git a/src/linkto.rs b/src/linkto.rs index 62feb0f..254b85c 100644 --- a/src/linkto.rs +++ b/src/linkto.rs @@ -1,6 +1,6 @@ -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::AsyncRead; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::AsyncReadExt; use crate::content::linkto; use crate::errors::{Error, IoErrorExt, Result}; @@ -8,9 +8,9 @@ use crate::{index, WriteOpts}; use ssri::{Algorithm, Integrity}; use std::io::Read; use std::path::{Path, PathBuf}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::pin::Pin; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::task::{Context as TaskContext, Poll}; const BUF_SIZE: usize = 16 * 1024; @@ -30,7 +30,7 @@ const PROBE_SIZE: usize = 8; /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn link_to(cache: P, key: K, target: T) -> Result where P: AsRef, @@ -54,7 +54,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn link_to_hash(cache: P, target: T) -> Result where P: AsRef, @@ -110,7 +110,7 @@ where /// `SyncToLinker` instances. impl WriteOpts { /// Opens the target file handle for reading, returning a ToLinker instance. - #[cfg(any(feature = "async-std", feature = "tokio"))] + #[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn link_to(self, cache: P, key: K, target: T) -> Result where P: AsRef, @@ -141,7 +141,7 @@ impl WriteOpts { /// Opens the target file handle for reading, without a key, returning a /// ToLinker instance. - #[cfg(any(feature = "async-std", feature = "tokio"))] + #[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn link_to_hash(self, cache: P, target: T) -> Result where P: AsRef, @@ -217,7 +217,7 @@ impl WriteOpts { /// /// Make sure to call `.commit()` when done reading to actually add the file to /// the cache. -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub struct ToLinker { cache: PathBuf, key: Option, @@ -226,7 +226,7 @@ pub struct ToLinker { opts: WriteOpts, } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl AsyncRead for ToLinker { #[cfg(feature = "async-std")] fn poll_read( @@ -250,6 +250,17 @@ impl AsyncRead for ToLinker { self.read += buf.filled().len() - pre_len; Poll::Ready(Ok(())) } + + #[cfg(feature = "smol")] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut [u8], + ) -> Poll> { + let amt = futures::ready!(Pin::new(&mut self.linker).poll_read(cx, buf))?; + self.read += amt; + Poll::Ready(Ok(amt)) + } } fn filesize(target: &Path) -> Result { @@ -259,7 +270,7 @@ fn filesize(target: &Path) -> Result { .len() as usize) } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl ToLinker { /// Creates a new asynchronous readable file handle into the cache. pub async fn open(cache: P, key: K, target: T) -> Result @@ -493,6 +504,10 @@ mod tests { #[cfg(feature = "async-std")] use async_attributes::test as async_test; + #[cfg(feature = "smol")] + use macro_rules_attribute::apply; + #[cfg(feature = "smol")] + use smol_macros::test; #[cfg(feature = "tokio")] use tokio::test as async_test; @@ -520,6 +535,20 @@ mod tests { assert_eq!(buf, b"hello world"); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_link() { + let tmp = tempfile::tempdir().unwrap(); + let target = create_tmpfile(&tmp, b"hello world"); + + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + crate::link_to(&dir, "my-key", target).await.unwrap(); + + let buf = crate::read(&dir, "my-key").await.unwrap(); + assert_eq!(buf, b"hello world"); + } + #[cfg(any(feature = "async-std", feature = "tokio"))] #[async_test] async fn test_link_to_hash() { @@ -534,6 +563,20 @@ mod tests { assert_eq!(buf, b"hello world"); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_link_to_hash() { + let tmp = tempfile::tempdir().unwrap(); + let target = create_tmpfile(&tmp, b"hello world"); + + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let sri = crate::link_to_hash(&dir, target).await.unwrap(); + + let buf = crate::read_hash(&dir, &sri).await.unwrap(); + assert_eq!(buf, b"hello world"); + } + #[test] fn test_link_to_sync() { let tmp = tempfile::tempdir().unwrap(); @@ -579,6 +622,25 @@ mod tests { assert_eq!(buf, b"hello world"); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_open() { + let tmp = tempfile::tempdir().unwrap(); + let target = create_tmpfile(&tmp, b"hello world"); + + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let mut handle = crate::ToLinker::open(&dir, "my-key", target).await.unwrap(); + + let mut buf = Vec::new(); + handle.read_to_end(&mut buf).await.unwrap(); + handle.commit().await.unwrap(); + assert_eq!(buf, b"hello world"); + + let buf = crate::read_sync(&dir, "my-key").unwrap(); + assert_eq!(buf, b"hello world"); + } + #[cfg(any(feature = "async-std", feature = "tokio"))] #[async_test] async fn test_open_hash() { @@ -598,6 +660,25 @@ mod tests { assert_eq!(buf, b"hello world"); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_open_hash() { + let tmp = tempfile::tempdir().unwrap(); + let target = create_tmpfile(&tmp, b"hello world"); + + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let mut handle = crate::ToLinker::open_hash(&dir, target).await.unwrap(); + + let mut buf = Vec::new(); + handle.read_to_end(&mut buf).await.unwrap(); + let sri = handle.commit().await.unwrap(); + assert_eq!(buf, b"hello world"); + + let buf = crate::read_hash_sync(&dir, &sri).unwrap(); + assert_eq!(buf, b"hello world"); + } + #[test] fn test_open_sync() { let tmp = tempfile::tempdir().unwrap(); diff --git a/src/put.rs b/src/put.rs index 5dc7e60..dd758cc 100644 --- a/src/put.rs +++ b/src/put.rs @@ -1,19 +1,19 @@ //! Functions for writing to cache. use std::io::prelude::*; use std::path::{Path, PathBuf}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::pin::Pin; use serde_json::Value; use ssri::{Algorithm, Integrity}; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use crate::async_lib::{AsyncWrite, AsyncWriteExt}; use crate::content::write; use crate::errors::{Error, IoErrorExt, Result}; use crate::index; -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] use std::task::{Context as TaskContext, Poll}; /// Writes `data` to the `cache`, indexing it under `key`. @@ -28,7 +28,7 @@ use std::task::{Context as TaskContext, Poll}; /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn write(cache: P, key: K, data: D) -> Result where P: AsRef, @@ -51,7 +51,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn write_with_algo( algo: Algorithm, cache: P, @@ -89,7 +89,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn write_hash(cache: P, data: D) -> Result where P: AsRef, @@ -111,7 +111,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn write_hash_with_algo(algo: Algorithm, cache: P, data: D) -> Result where P: AsRef, @@ -132,7 +132,7 @@ where inner(algo, cache.as_ref(), data.as_ref()).await } /// A reference to an open file writing to the cache. -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub struct Writer { cache: PathBuf, key: Option, @@ -141,7 +141,7 @@ pub struct Writer { opts: WriteOpts, } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl AsyncWrite for Writer { fn poll_write( mut self: Pin<&mut Self>, @@ -169,9 +169,14 @@ impl AsyncWrite for Writer { ) -> Poll> { Pin::new(&mut self.writer).poll_shutdown(cx) } + + #[cfg(feature = "smol")] + fn poll_close(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.writer).poll_close(cx) + } } -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] impl Writer { /// Creates a new writable file handle into the cache. /// @@ -372,7 +377,7 @@ impl WriteOpts { } /// Opens the file handle for writing, returning an Writer instance. - #[cfg(any(feature = "async-std", feature = "tokio"))] + #[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn open(self, cache: P, key: K) -> Result where P: AsRef, @@ -396,7 +401,7 @@ impl WriteOpts { } /// Opens the file handle for writing, without a key returning an Writer instance. - #[cfg(any(feature = "async-std", feature = "tokio"))] + #[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn open_hash

(self, cache: P) -> Result where P: AsRef, @@ -607,6 +612,10 @@ impl SyncWriter { mod tests { #[cfg(feature = "async-std")] use async_attributes::test as async_test; + #[cfg(feature = "smol")] + use macro_rules_attribute::apply; + #[cfg(feature = "smol")] + use smol_macros::test; #[cfg(feature = "tokio")] use tokio::test as async_test; @@ -620,6 +629,16 @@ mod tests { assert_eq!(data, b"hello"); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn round_trip() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + crate::write(&dir, "hello", b"hello").await.unwrap(); + let data = crate::read(&dir, "hello").await.unwrap(); + assert_eq!(data, b"hello"); + } + #[test] fn round_trip_sync() { let tmp = tempfile::tempdir().unwrap(); @@ -659,4 +678,21 @@ mod tests { String::from_utf8(bytes).expect("we wrote valid utf8 but did not read valid utf8 back"); assert_eq!(result, original, "we did not read back what we wrote"); } + + #[cfg(feature = "smol")] + #[apply(test!)] + async fn hash_write_async() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let original = format!("hello world{}", 12); + let integrity = crate::write_hash(&dir, &original) + .await + .expect("should be able to write a hash asynchronously"); + let bytes = crate::read_hash(&dir, &integrity) + .await + .expect("should be able to read back what we wrote"); + let result = + String::from_utf8(bytes).expect("we wrote valid utf8 but did not read valid utf8 back"); + assert_eq!(result, original, "we did not read back what we wrote"); + } } diff --git a/src/rm.rs b/src/rm.rs index bc15000..7132033 100644 --- a/src/rm.rs +++ b/src/rm.rs @@ -31,7 +31,7 @@ use crate::index; /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn remove(cache: P, key: K) -> Result<()> where P: AsRef, @@ -64,7 +64,7 @@ where /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn remove_hash>(cache: P, sri: &Integrity) -> Result<()> { rm::rm_async(cache.as_ref(), sri).await } @@ -91,7 +91,7 @@ pub async fn remove_hash>(cache: P, sri: &Integrity) -> Result<() /// Ok(()) /// } /// ``` -#[cfg(any(feature = "async-std", feature = "tokio"))] +#[cfg(any(feature = "async-std", feature = "tokio", feature = "smol"))] pub async fn clear>(cache: P) -> Result<()> { async fn inner(cache: &Path) -> Result<()> { for entry in cache @@ -213,6 +213,10 @@ mod tests { #[cfg(feature = "async-std")] use async_attributes::test as async_test; + #[cfg(feature = "smol")] + use macro_rules_attribute::apply; + #[cfg(feature = "smol")] + use smol_macros::test; #[cfg(feature = "tokio")] use tokio::test as async_test; @@ -234,6 +238,24 @@ mod tests { }); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_remove() { + futures::executor::block_on(async { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let sri = crate::write(&dir, "key", b"my-data").await.unwrap(); + + crate::remove(&dir, "key").await.unwrap(); + + let entry = crate::metadata(&dir, "key").await.unwrap(); + assert_eq!(entry, None); + + let data_exists = crate::exists(&dir, &sri).await; + assert!(data_exists); + }); + } + #[cfg(any(feature = "async-std", feature = "tokio"))] #[async_test] async fn test_remove_data() { @@ -252,6 +274,24 @@ mod tests { }); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_remove_data() { + futures::executor::block_on(async { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let sri = crate::write(&dir, "key", b"my-data").await.unwrap(); + + crate::remove_hash(&dir, &sri).await.unwrap(); + + let entry = crate::metadata(&dir, "key").await.unwrap(); + assert!(entry.is_some()); + + let data_exists = crate::exists(&dir, &sri).await; + assert!(!data_exists); + }); + } + #[cfg(any(feature = "async-std", feature = "tokio"))] #[async_test] async fn test_clear() { @@ -270,6 +310,24 @@ mod tests { }); } + #[cfg(feature = "smol")] + #[apply(test!)] + async fn test_clear() { + futures::executor::block_on(async { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let sri = crate::write(&dir, "key", b"my-data").await.unwrap(); + + crate::clear(&dir).await.unwrap(); + + let entry = crate::metadata(&dir, "key").await.unwrap(); + assert!(entry.is_none()); + + let data_exists = crate::exists(&dir, &sri).await; + assert!(!data_exists); + }); + } + #[test] fn test_remove_sync() { let tmp = tempfile::tempdir().unwrap();