This commit is contained in:
Christian Haynes 2026-06-02 19:56:12 -03:00 committed by GitHub
commit ae694e48e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 747 additions and 395 deletions

View File

@ -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:
@ -42,9 +42,9 @@ jobs:
override: true
- name: Clippy
run: cargo clippy -- -D warnings
- name: Check (async-std)
- name: Check (smol)
run: cargo check --features link_to
- name: Run tests (async-std)
- name: Run tests (smol)
run: cargo test --verbose --features link_to
- name: Check (Tokio)
run: cargo check --no-default-features --features tokio-runtime,link_to

View File

@ -11,12 +11,12 @@ readme = "README.md"
categories = ["caching", "filesystem"]
[dependencies]
async-std = { version = "1.10.0", features = ["unstable"], optional = true }
digest = "0.10.6"
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"
@ -40,9 +40,10 @@ walkdir = "2.3.2"
libc = { version = "0.2.144", optional = true }
[dev-dependencies]
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",
@ -56,8 +57,8 @@ name = "benchmarks"
harness = false
[features]
default = ["async-std", "mmap"]
default = ["smol-runtime", "mmap"]
mmap = ["memmap2", "libc"]
async-std = ["dep:async-std", "futures"]
link_to = []
tokio-runtime = ["tokio", "tokio-stream", "futures"]
smol-runtime = ["smol", "futures"]

View File

@ -1,13 +1,10 @@
#[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"))]
use tokio::fs as afs;
#[cfg(all(test, feature = "async-std"))]
pub use async_std::task::block_on;
#[cfg(all(test, feature = "tokio"))]
lazy_static::lazy_static! {
static ref TOKIO_RUNTIME: tokio::runtime::Runtime = tokio::runtime::Runtime::new().unwrap();
@ -22,6 +19,15 @@ where
TOKIO_RUNTIME.block_on(future)
}
#[cfg(all(test, feature = "smol"))]
#[inline]
pub fn block_on<F, T>(future: F) -> T
where
F: std::future::Future<Output = T>,
{
smol::block_on(future)
}
use std::fs::{self, File};
use std::io::prelude::*;
@ -62,7 +68,7 @@ fn baseline_read_many_sync(c: &mut Criterion) {
});
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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 +81,7 @@ fn baseline_read_async(c: &mut Criterion) {
});
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
fn baseline_read_many_async(c: &mut Criterion) {
let tmp = tempfile::tempdir().unwrap();
let paths: Vec<_> = (0..)
@ -190,7 +196,7 @@ fn read_hash_sync_big_data_xxh3(c: &mut Criterion) {
});
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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 +218,7 @@ fn read_hash_many_async(c: &mut Criterion) {
});
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
fn read_hash_async(c: &mut Criterion) {
let tmp = tempfile::tempdir().unwrap();
let cache = tmp.path().to_owned();
@ -223,7 +229,7 @@ fn read_hash_async(c: &mut Criterion) {
});
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
fn read_async(c: &mut Criterion) {
let tmp = tempfile::tempdir().unwrap();
let cache = tmp.path().to_owned();
@ -234,7 +240,7 @@ fn read_async(c: &mut Criterion) {
});
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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 +284,7 @@ fn write_hash_xxh3(c: &mut Criterion) {
});
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
fn write_hash_async(c: &mut Criterion) {
let tmp = tempfile::tempdir().unwrap();
let cache = tmp.path().to_owned();
@ -293,7 +299,7 @@ fn write_hash_async(c: &mut Criterion) {
});
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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 +331,7 @@ fn create_tmpfile(tmp: &tempfile::TempDir, buf: &[u8]) -> PathBuf {
}
#[cfg(feature = "link_to")]
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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 +354,7 @@ 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 = "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 +411,7 @@ criterion_group!(
read_hash_sync_big_data_xxh3,
);
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
criterion_group!(
benches_async,
baseline_read_async,
@ -418,23 +424,17 @@ 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 = "tokio", feature = "smol")))]
criterion_group!(link_to_benches_async, link_to_async, link_to_hash_async,);
#[cfg(feature = "link_to")]
criterion_group!(link_to_benches, link_to_sync, link_to_hash_sync);
#[cfg(all(
feature = "link_to",
not(any(feature = "async-std", feature = "tokio"))
))]
#[cfg(all(feature = "link_to", not(any(feature = "tokio", feature = "smol"))))]
criterion_main!(benches, link_to_benches);
#[cfg(all(
not(feature = "link_to"),
any(feature = "async-std", feature = "tokio")
))]
#[cfg(all(not(feature = "link_to"), any(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 = "tokio", feature = "smol")))]
criterion_main!(
benches,
benches_async,
@ -443,6 +443,6 @@ criterion_main!(
);
#[cfg(all(
not(feature = "link_to"),
not(any(feature = "async-std", feature = "tokio"))
not(any(feature = "tokio", feature = "smol"))
))]
criterion_main!(benches);

View File

@ -2,23 +2,23 @@
@help:
just -l
# Run tests on both runtimes with cargo nextest
# Run tests on all runtimes with cargo nextest
@test:
echo "----------\nasync-std:\n"
echo "----------\nsmol:\n"
cargo nextest run
echo "\n----------\ntokio:\n"
cargo nextest run --no-default-features --features tokio-runtime
# Run benchmarks with `cargo bench`
@bench:
echo "----------\nasync-std:\n"
echo "----------\nsmol:\n"
cargo bench
echo "\n----------\ntokio:\n"
cargo bench --no-default-features --features tokio-runtime
# Run benchmarks with `cargo criterion`
@criterion:
echo "----------\nasync-std:\n"
echo "----------\nsmol:\n"
cargo criterion
echo "\n----------\ntokio:\n"
cargo criterion --no-default-features --features tokio-runtime

View File

@ -1,79 +1,79 @@
#[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")]
#[cfg(feature = "smol")]
pub use futures::io::AsyncRead;
#[cfg(feature = "tokio")]
pub use tokio::io::AsyncRead;
#[cfg(feature = "async-std")]
#[cfg(feature = "smol")]
pub use futures::io::AsyncReadExt;
#[cfg(feature = "tokio")]
pub use tokio::io::AsyncReadExt;
#[cfg(feature = "async-std")]
#[cfg(feature = "smol")]
pub use futures::io::AsyncBufReadExt;
#[cfg(feature = "tokio")]
pub use tokio::io::AsyncBufReadExt;
#[cfg(feature = "async-std")]
#[cfg(feature = "smol")]
pub use futures::io::AsyncWrite;
#[cfg(feature = "tokio")]
pub use tokio::io::AsyncWrite;
#[cfg(feature = "async-std")]
#[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;
#[cfg(feature = "async-std")]
#[cfg(feature = "smol")]
#[inline]
pub fn lines_to_stream<R>(lines: futures::io::Lines<R>) -> futures::io::Lines<R> {
lines
@ -84,31 +84,31 @@ pub fn lines_to_stream<R>(lines: tokio::io::Lines<R>) -> tokio_stream::wrappers:
tokio_stream::wrappers::LinesStream::new(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;
#[cfg(feature = "async-std")]
pub use async_std::task::JoinHandle;
#[cfg(feature = "async-std")]
#[inline]
pub fn unwrap_joinhandle_value<T>(value: T) -> T {
value
}
#[cfg(feature = "smol")]
pub use smol::Task as JoinHandle;
#[cfg(feature = "tokio")]
pub use tokio::task::JoinHandle;
#[cfg(feature = "smol")]
#[inline]
pub fn unwrap_joinhandle_value<T>(value: T) -> T {
value
}
#[cfg(feature = "tokio")]
#[inline]
pub fn unwrap_joinhandle_value<T>(value: T) -> T {
value
}
use crate::errors::IoErrorExt;
use tempfile::NamedTempFile;
use crate::errors::IoErrorExt;
#[cfg(feature = "async-std")]
#[cfg(feature = "smol")]
#[inline]
pub async fn create_named_tempfile(
tmp_path: std::path::PathBuf,

View File

@ -2,13 +2,11 @@ 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 = "tokio", feature = "smol"))]
use std::pin::Pin;
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
use std::task::{Context, Poll};
#[cfg(any(feature = "async-std", feature = "tokio"))]
use crate::async_lib::AsyncRead;
use crate::content::path;
use crate::errors::{IoErrorExt, Result};
@ -106,7 +104,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 = "tokio", feature = "smol"))]
pub struct AsyncToLinker {
/// The path to the target file that will be symlinked from the cache.
target: PathBuf,
@ -118,22 +116,8 @@ pub struct AsyncToLinker {
builder: IntegrityOpts,
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
impl AsyncRead for AsyncToLinker {
#[cfg(feature = "async-std")]
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
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(feature = "tokio")]
#[cfg(feature = "tokio")]
impl tokio::io::AsyncRead for AsyncToLinker {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
@ -148,7 +132,22 @@ impl AsyncRead for AsyncToLinker {
}
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "smol")]
impl futures::io::AsyncRead for AsyncToLinker {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
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 = "tokio", feature = "smol"))]
impl AsyncToLinker {
pub async fn new(cache: &Path, algo: Algorithm, target: &Path) -> Result<Self> {
let file = crate::async_lib::File::open(target)
@ -174,12 +173,14 @@ mod tests {
use super::*;
#[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")]
#[cfg(feature = "smol")]
use futures::io::AsyncReadExt;
#[cfg(feature = "tokio")]
use tokio::io::AsyncReadExt;
@ -222,7 +223,7 @@ mod tests {
assert_eq!(std::fs::read(cpath).unwrap(), b"hello world");
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn basic_async_link() {
let tmp = tempfile::tempdir().unwrap();
@ -254,4 +255,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<u8> = 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");
}
}

View File

@ -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 = "tokio", feature = "smol"))]
use std::pin::Pin;
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
use std::task::{Context, Poll};
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
use crate::async_lib::AsyncReadExt;
use ssri::{Algorithm, Integrity, IntegrityChecker};
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
use crate::async_lib::AsyncRead;
use crate::content::path;
use crate::errors::{IoErrorExt, Result};
@ -35,25 +35,14 @@ impl Reader {
}
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub struct AsyncReader {
fd: crate::async_lib::File,
checker: IntegrityChecker,
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
impl AsyncRead for AsyncReader {
#[cfg(feature = "async-std")]
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
let amt = futures::ready!(Pin::new(&mut self.fd).poll_read(cx, buf))?;
self.checker.input(&buf[..amt]);
Poll::Ready(Ok(amt))
}
#[cfg(feature = "tokio")]
fn poll_read(
mut self: Pin<&mut Self>,
@ -69,9 +58,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<std::io::Result<usize>> {
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 = "tokio", feature = "smol"))]
impl AsyncReader {
pub fn check(self) -> Result<Algorithm> {
Ok(self.checker.result()?)
@ -91,7 +91,7 @@ pub fn open(cache: &Path, sri: Integrity) -> Result<Reader> {
})
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn open_async(cache: &Path, sri: Integrity) -> Result<AsyncReader> {
let cpath = path::content_path(cache, &sri);
Ok(AsyncReader {
@ -117,7 +117,7 @@ pub fn read(cache: &Path, sri: &Integrity) -> Result<Vec<u8>> {
Ok(ret)
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn read_async<'a>(cache: &'a Path, sri: &'a Integrity) -> Result<Vec<u8>> {
let cpath = path::content_path(cache, sri);
let ret = crate::async_lib::read(&cpath).await.with_context(|| {
@ -160,7 +160,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 = "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 +214,7 @@ pub fn copy(cache: &Path, sri: &Integrity, to: &Path) -> Result<u64> {
Ok(size as u64)
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn copy_unchecked_async<'a>(
cache: &'a Path,
sri: &'a Integrity,
@ -230,7 +230,7 @@ pub async fn copy_unchecked_async<'a>(
})
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn copy_async<'a>(cache: &'a Path, sri: &'a Integrity, to: &'a Path) -> Result<u64> {
let mut reader = open_async(cache, sri.clone()).await?;
let mut buf: [u8; 1024] = [0; 1024];
@ -285,7 +285,7 @@ pub fn hard_link(cache: &Path, sri: &Integrity, to: &Path) -> Result<()> {
Ok(())
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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 +315,7 @@ pub fn has_content(cache: &Path, sri: &Integrity) -> Option<Integrity> {
}
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn has_content_async(cache: &Path, sri: &Integrity) -> Option<Integrity> {
if crate::async_lib::metadata(path::content_path(cache, sri))
.await

View File

@ -16,7 +16,7 @@ pub fn rm(cache: &Path, sri: &Integrity) -> Result<()> {
Ok(())
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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

View File

@ -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 = "tokio", feature = "smol"))]
use std::pin::Pin;
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
use std::sync::Mutex;
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
use std::task::{Context, Poll};
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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 = "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 = "tokio", feature = "smol"))]
pub struct AsyncWriter(Mutex<State>);
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
enum State {
Idle(Option<Inner>),
Busy(JoinHandle<State>),
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
struct Inner {
cache: PathBuf,
builder: IntegrityOpts,
@ -148,13 +148,13 @@ struct Inner {
last_op: Option<Operation>,
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
enum Operation {
Write(std::io::Result<usize>),
Flush(std::io::Result<()>),
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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 = "tokio", feature = "smol"))]
impl AsyncWrite for AsyncWriter {
fn poll_write(
self: Pin<&mut Self>,
@ -398,13 +398,13 @@ impl AsyncWrite for AsyncWriter {
}
}
#[cfg(feature = "async-std")]
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
#[cfg(feature = "tokio")]
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
self.poll_close_impl(cx)
}
#[cfg(feature = "tokio")]
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
#[cfg(feature = "smol")]
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
self.poll_close_impl(cx)
}
}
@ -431,7 +431,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 = "tokio", feature = "smol"))]
impl AsyncWriter {
#[inline]
fn poll_close_impl(
@ -491,20 +491,18 @@ fn make_mmap(tmpfile: &mut NamedTempFile, size: Option<usize>) -> Result<Option<
#[cfg(feature = "mmap")]
#[cfg(target_os = "linux")]
fn allocate_file(file: &std::fs::File, size: usize) -> 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 +520,14 @@ fn make_mmap(_: &mut NamedTempFile, _: Option<usize>) -> Result<Option<MmapMut>>
#[cfg(test)]
mod tests {
use super::*;
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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;
@ -545,7 +545,7 @@ mod tests {
);
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn basic_async_write() {
let tmp = tempfile::tempdir().unwrap();
@ -561,4 +561,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"
);
}
}

View File

@ -59,5 +59,5 @@ impl<T> IoErrorExt<T> for std::result::Result<T, serde_json::Error> {
}
pub fn io_error(err: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::Other, err)
std::io::Error::other(err)
}

View File

@ -1,13 +1,13 @@
//! Functions for reading from cache.
use std::path::Path;
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
use std::pin::Pin;
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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 = "tokio", feature = "smol"))]
use crate::async_lib::AsyncRead;
use crate::content::read;
use crate::errors::{Error, Result};
@ -21,14 +21,23 @@ 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 = "tokio", feature = "smol"))]
pub struct Reader {
reader: read::AsyncReader,
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
impl AsyncRead for Reader {
#[cfg(feature = "async-std")]
#[cfg(feature = "tokio")]
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut TaskContext<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.reader).poll_read(cx, buf)
}
#[cfg(feature = "smol")]
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut TaskContext<'_>,
@ -36,18 +45,9 @@ impl AsyncRead for Reader {
) -> Poll<std::io::Result<usize>> {
Pin::new(&mut self.reader).poll_read(cx, buf)
}
#[cfg(feature = "tokio")]
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut TaskContext<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<tokio::io::Result<()>> {
Pin::new(&mut self.reader).poll_read(cx, buf)
}
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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
@ -59,10 +59,11 @@ impl Reader {
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
/// use futures::io::AsyncReadExt;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let mut fd = cacache::Reader::open("./my-cache", "my-key").await?;
/// let mut str = String::new();
@ -81,10 +82,11 @@ impl Reader {
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
/// use futures::io::AsyncReadExt;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let mut fd = cacache::Reader::open("./my-cache", "my-key").await?;
/// let mut str = String::new();
@ -113,10 +115,11 @@ impl Reader {
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
/// use futures::io::AsyncReadExt;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let sri = cacache::write("./my-cache", "key", b"hello world").await?;
/// let mut fd = cacache::Reader::open_hash("./my-cache", sri).await?;
@ -142,16 +145,16 @@ impl Reader {
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let data: Vec<u8> = cacache::read("./my-cache", "my-key").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn read<P, K>(cache: P, key: K) -> Result<Vec<u8>>
where
P: AsRef<Path>,
@ -172,17 +175,17 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let sri = cacache::write("./my-cache", "my-key", b"hello").await?;
/// let data: Vec<u8> = cacache::read_hash("./my-cache", &sri).await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn read_hash<P>(cache: P, sri: &Integrity) -> Result<Vec<u8>>
where
P: AsRef<Path>,
@ -195,16 +198,16 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// cacache::copy("./my-cache", "my-key", "./data.txt").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn copy<P, K, Q>(cache: P, key: K, to: Q) -> Result<u64>
where
P: AsRef<Path>,
@ -226,16 +229,16 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// cacache::copy_unchecked("./my-cache", "my-key", "./data.txt").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn copy_unchecked<P, K, Q>(cache: P, key: K, to: Q) -> Result<u64>
where
P: AsRef<Path>,
@ -257,17 +260,17 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let sri = cacache::write("./my-cache", "my-key", b"hello world").await?;
/// cacache::copy_hash("./my-cache", &sri, "./data.txt").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn copy_hash<P, Q>(cache: P, sri: &Integrity, to: Q) -> Result<u64>
where
P: AsRef<Path>,
@ -281,17 +284,17 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let sri = cacache::write("./my-cache", "my-key", b"hello world").await?;
/// cacache::copy_hash_unchecked("./my-cache", &sri, "./data.txt").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn copy_hash_unchecked<P, Q>(cache: P, sri: &Integrity, to: Q) -> Result<u64>
where
P: AsRef<Path>,
@ -310,16 +313,16 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// cacache::reflink("./my-cache", "my-key", "./data.txt").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn reflink<P, K, Q>(cache: P, key: K, to: Q) -> Result<()>
where
P: AsRef<Path>,
@ -347,16 +350,16 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// cacache::reflink_unchecked("./my-cache", "my-key", "./data.txt").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn reflink_unchecked<P, K, Q>(cache: P, key: K, to: Q) -> Result<()>
where
P: AsRef<Path>,
@ -383,17 +386,17 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let sri = cacache::write("./my-cache", "my-key", b"hello world").await?;
/// cacache::reflink_hash("./my-cache", &sri, "./data.txt").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn reflink_hash<P, Q>(cache: P, sri: &Integrity, to: Q) -> Result<()>
where
P: AsRef<Path>,
@ -403,7 +406,7 @@ where
}
/// Hard links a cache entry by hash to a specified location.
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn hard_link_hash<P, Q>(cache: P, sri: &Integrity, to: Q) -> Result<()>
where
P: AsRef<Path>,
@ -413,7 +416,7 @@ where
}
/// Hard links a cache entry by key to a specified location.
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn hard_link<P, K, Q>(cache: P, key: K, to: Q) -> Result<()>
where
P: AsRef<Path>,
@ -435,7 +438,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 = "tokio", feature = "smol"))]
pub async fn metadata<P, K>(cache: P, key: K) -> Result<Option<Metadata>>
where
P: AsRef<Path>,
@ -445,7 +448,7 @@ where
}
/// Returns true if the given hash exists in the cache.
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn exists<P: AsRef<Path>>(cache: P, sri: &Integrity) -> bool {
read::has_content_async(cache.as_ref(), sri).await.is_some()
}
@ -715,11 +718,7 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
///
/// #[async_attributes::main]
/// async fn main() -> cacache::Result<()> {
/// fn main() -> cacache::Result<()> {
/// cacache::reflink_sync("./my-cache", "my-key", "./data.txt")?;
/// Ok(())
/// }
@ -750,11 +749,7 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
///
/// #[async_attributes::main]
/// async fn main() -> cacache::Result<()> {
/// fn main() -> cacache::Result<()> {
/// let sri = cacache::write_sync("./my-cache", "my-key", b"hello world")?;
/// cacache::reflink_hash_sync("./my-cache", &sri, "./data.txt")?;
/// Ok(())
@ -779,11 +774,7 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
///
/// #[async_attributes::main]
/// async fn main() -> cacache::Result<()> {
/// fn main() -> cacache::Result<()> {
/// let sri = cacache::write_sync("./my-cache", "my-key", b"hello world")?;
/// cacache::reflink_hash_unchecked_sync("./my-cache", &sri, "./data.txt")?;
/// Ok(())
@ -808,11 +799,7 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
///
/// #[async_attributes::main]
/// async fn main() -> cacache::Result<()> {
/// fn main() -> cacache::Result<()> {
/// cacache::reflink_unchecked_sync("./my-cache", "my-key", "./data.txt")?;
/// Ok(())
/// }
@ -912,16 +899,18 @@ pub fn exists_sync<P: AsRef<Path>>(cache: P, sri: &Integrity) -> bool {
#[cfg(test)]
mod tests {
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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;
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn test_open() {
let tmp = tempfile::tempdir().unwrap();
@ -935,7 +924,21 @@ mod tests {
assert_eq!(str, String::from("hello world"));
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[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(feature = "tokio")]
#[async_test]
async fn test_open_hash() {
let tmp = tempfile::tempdir().unwrap();
@ -949,6 +952,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::*;
@ -977,7 +994,7 @@ mod tests {
assert_eq!(str, String::from("hello world"));
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn test_read() {
let tmp = tempfile::tempdir().unwrap();
@ -988,7 +1005,18 @@ mod tests {
assert_eq!(data, b"hello world");
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[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(feature = "tokio")]
#[async_test]
async fn test_read_hash() {
let tmp = tempfile::tempdir().unwrap();
@ -999,6 +1027,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();
@ -1019,7 +1058,7 @@ mod tests {
assert_eq!(data, b"hello world");
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn test_copy() {
let tmp = tempfile::tempdir().unwrap();
@ -1032,7 +1071,20 @@ mod tests {
assert_eq!(data, b"hello world");
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[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(feature = "tokio")]
#[async_test]
async fn test_copy_hash() {
let tmp = tempfile::tempdir().unwrap();
@ -1045,6 +1097,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();

View File

@ -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 = "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 = "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<Integrity> {
.unwrap())
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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<Integrity> {
let bucket = bucket_path(cache, key);
@ -175,7 +175,7 @@ pub fn find(cache: &Path, key: &str) -> Result<Option<Metadata>> {
}))
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
/// Asynchronous raw index Metadata access.
pub async fn find_async(cache: &Path, key: &str) -> Result<Option<Metadata>> {
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 = "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<Vec<SerializableMetadata>> {
})
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
async fn bucket_entries_async(bucket: &Path) -> std::io::Result<Vec<SerializableMetadata>> {
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 = "tokio", feature = "smol"))]
pub async fn remove<P, K>(self, cache: P, key: K) -> Result<()>
where
P: AsRef<Path>,
@ -452,8 +452,10 @@ mod tests {
use super::*;
use serde_json::json;
#[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;
@ -480,7 +482,7 @@ mod tests {
assert_eq!(entry, MOCK_ENTRY);
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn insert_async_basic() {
let tmp = tempfile::tempdir().unwrap();
@ -495,6 +497,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();
@ -537,7 +554,7 @@ mod tests {
assert_eq!(find(&dir, "hello").unwrap(), None);
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn delete_async_basic() {
let tmp = tempfile::tempdir().unwrap();
@ -552,6 +569,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();
@ -570,7 +602,7 @@ mod tests {
assert!(!content.exists());
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn delete_fully_async() {
let tmp = tempfile::tempdir().unwrap();
@ -590,6 +622,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();
@ -612,7 +664,7 @@ mod tests {
);
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn round_trip_async() {
let tmp = tempfile::tempdir().unwrap();
@ -639,6 +691,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();

View File

@ -29,14 +29,16 @@
//!
//! ## Examples
//!
//! Un-suffixed APIs are all async, using
//! [`async-std`](https://crates.io/crates/async-std). They let you put data
//! Un-suffixed APIs are all async, using asynchronous runtimes like
//! [`tokio`](https://crates.io/crates/tokio) or
//! [`smol`](https://crates.io/crates/smol). They let you put data
//! in and get it back out -- asynchronously!
//!
//! ```no_run
//! use async_attributes;
//! use macro_rules_attribute::apply;
//! use smol_macros::main;
//!
//! #[async_attributes::main]
//! #[apply(main!)]
//! async fn main() -> cacache::Result<()> {
//! // Data goes in...
//! cacache::write("./my-cache", "key", b"hello").await?;
@ -58,9 +60,10 @@
//! than doing key lookups:
//!
//! ```no_run
//! use async_attributes;
//! use macro_rules_attribute::apply;
//! use smol_macros::main;
//!
//! #[async_attributes::main]
//! #[apply(main!)]
//! async fn main() -> cacache::Result<()> {
//! // Data goes in...
//! let sri = cacache::write("./my-cache", "key", b"hello").await?;
@ -79,10 +82,11 @@
//! an API reminiscent of `std::fs::OpenOptions`:
//!
//! ```no_run
//! use async_attributes;
//! use async_std::prelude::*;
//! use macro_rules_attribute::apply;
//! use smol_macros::main;
//! use futures::io::{AsyncWriteExt, AsyncReadExt};
//!
//! #[async_attributes::main]
//! #[apply(main!)]
//! async fn main() -> cacache::Result<()> {
//! let mut fd = cacache::Writer::create("./my-cache", "key").await?;
//! for _ in 0..10 {
@ -142,7 +146,10 @@
//! the same suffixes as the other APIs.
//!
//! ```no_run
//! #[async_attributes::main]
//! use macro_rules_attribute::apply;
//! use smol_macros::main;
//!
//! #[apply(main!)]
//! async fn main() -> cacache::Result<()> {
//! #[cfg(feature = "link_to")]
//! cacache::link_to("./my-cache", "key", "/path/to/my-other-file.txt").await?;
@ -153,13 +160,15 @@
//! ```
#![warn(missing_docs)]
#[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 = "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 = "tokio", feature = "smol"))]
mod async_lib;
mod content;

View File

@ -1,6 +1,4 @@
#[cfg(any(feature = "async-std", feature = "tokio"))]
use crate::async_lib::AsyncRead;
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
use crate::async_lib::AsyncReadExt;
use crate::content::linkto;
use crate::errors::{Error, IoErrorExt, Result};
@ -8,9 +6,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 = "tokio", feature = "smol"))]
use std::pin::Pin;
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
use std::task::{Context as TaskContext, Poll};
const BUF_SIZE: usize = 16 * 1024;
@ -21,16 +19,17 @@ const PROBE_SIZE: usize = 8;
///
/// ## Example
/// ```no_run
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
/// use std::path::Path;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// cacache::link_to("./my-cache", "my-key", "../my-other-files/my-file.tgz").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn link_to<P, K, T>(cache: P, key: K, target: T) -> Result<Integrity>
where
P: AsRef<Path>,
@ -45,16 +44,17 @@ where
///
/// ## Example
/// ```no_run
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
/// use std::path::Path;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// cacache::link_to_hash("./my-cache", "../my-other-files/my-file.tgz").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn link_to_hash<P, T>(cache: P, target: T) -> Result<Integrity>
where
P: AsRef<Path>,
@ -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 = "tokio", feature = "smol"))]
pub async fn link_to<P, K, T>(self, cache: P, key: K, target: T) -> Result<ToLinker>
where
P: AsRef<Path>,
@ -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 = "tokio", feature = "smol"))]
pub async fn link_to_hash<P, T>(self, cache: P, target: T) -> Result<ToLinker>
where
P: AsRef<Path>,
@ -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 = "tokio", feature = "smol"))]
pub struct ToLinker {
cache: PathBuf,
key: Option<String>,
@ -226,20 +226,8 @@ pub struct ToLinker {
opts: WriteOpts,
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
impl AsyncRead for ToLinker {
#[cfg(feature = "async-std")]
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut TaskContext<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
let amt = futures::ready!(Pin::new(&mut self.linker).poll_read(cx, buf))?;
self.read += amt;
Poll::Ready(Ok(amt))
}
#[cfg(feature = "tokio")]
#[cfg(feature = "tokio")]
impl tokio::io::AsyncRead for ToLinker {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut TaskContext<'_>,
@ -252,6 +240,19 @@ impl AsyncRead for ToLinker {
}
}
#[cfg(feature = "smol")]
impl futures::io::AsyncRead for ToLinker {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut TaskContext<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
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<usize> {
Ok(target
.metadata()
@ -259,7 +260,7 @@ fn filesize(target: &Path) -> Result<usize> {
.len() as usize)
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
impl ToLinker {
/// Creates a new asynchronous readable file handle into the cache.
pub async fn open<P, K, T>(cache: P, key: K, target: T) -> Result<Self>
@ -491,8 +492,10 @@ mod tests {
use super::*;
#[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;
@ -506,7 +509,7 @@ mod tests {
target
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn test_link() {
let tmp = tempfile::tempdir().unwrap();
@ -520,7 +523,21 @@ mod tests {
assert_eq!(buf, b"hello world");
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[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(feature = "tokio")]
#[async_test]
async fn test_link_to_hash() {
let tmp = tempfile::tempdir().unwrap();
@ -534,6 +551,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();
@ -560,7 +591,7 @@ mod tests {
assert_eq!(buf, b"hello world");
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn test_open() {
let tmp = tempfile::tempdir().unwrap();
@ -579,7 +610,26 @@ mod tests {
assert_eq!(buf, b"hello world");
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[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(feature = "tokio")]
#[async_test]
async fn test_open_hash() {
let tmp = tempfile::tempdir().unwrap();
@ -598,6 +648,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();

View File

@ -1,34 +1,35 @@
//! Functions for writing to cache.
use std::io::prelude::*;
use std::path::{Path, PathBuf};
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(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 = "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 = "tokio", feature = "smol"))]
use std::task::{Context as TaskContext, Poll};
/// Writes `data` to the `cache`, indexing it under `key`.
///
/// ## Example
/// ```no_run
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// cacache::write("./my-cache", "my-key", b"hello").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn write<P, D, K>(cache: P, key: K, data: D) -> Result<Integrity>
where
P: AsRef<Path>,
@ -43,15 +44,16 @@ where
///
/// ## Example
/// ```no_run
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// cacache::write_with_algo(cacache::Algorithm::Xxh3, "./my-cache", "my-key", b"hello").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn write_with_algo<P, D, K>(
algo: Algorithm,
cache: P,
@ -81,15 +83,16 @@ where
///
/// ## Example
/// ```no_run
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// cacache::write_hash("./my-cache", b"hello").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn write_hash<P, D>(cache: P, data: D) -> Result<Integrity>
where
P: AsRef<Path>,
@ -103,15 +106,16 @@ where
///
/// ## Example
/// ```no_run
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// cacache::write_hash_with_algo(cacache::Algorithm::Xxh3, "./my-cache", b"hello").await?;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn write_hash_with_algo<P, D>(algo: Algorithm, cache: P, data: D) -> Result<Integrity>
where
P: AsRef<Path>,
@ -132,7 +136,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 = "tokio", feature = "smol"))]
pub struct Writer {
cache: PathBuf,
key: Option<String>,
@ -141,7 +145,7 @@ pub struct Writer {
opts: WriteOpts,
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
impl AsyncWrite for Writer {
fn poll_write(
mut self: Pin<&mut Self>,
@ -157,11 +161,6 @@ impl AsyncWrite for Writer {
Pin::new(&mut self.writer).poll_flush(cx)
}
#[cfg(feature = "async-std")]
fn poll_close(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.writer).poll_close(cx)
}
#[cfg(feature = "tokio")]
fn poll_shutdown(
mut self: Pin<&mut Self>,
@ -169,18 +168,24 @@ impl AsyncWrite for Writer {
) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.writer).poll_shutdown(cx)
}
#[cfg(feature = "smol")]
fn poll_close(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.writer).poll_close(cx)
}
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
impl Writer {
/// Creates a new writable file handle into the cache.
///
/// ## Example
/// ```no_run
/// use async_attributes;
/// use async_std::prelude::*;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
/// use futures::io::AsyncWriteExt;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let mut fd = cacache::Writer::create("./my-cache", "my-key").await?;
/// fd.write_all(b"hello world").await.expect("Failed to write to cache");
@ -202,10 +207,11 @@ impl Writer {
///
/// ## Example
/// ```no_run
/// use async_attributes;
/// use async_std::prelude::*;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
/// use futures::io::AsyncWriteExt;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let mut fd = cacache::Writer::create_with_algo(cacache::Algorithm::Xxh3, "./my-cache", "my-key").await?;
/// fd.write_all(b"hello world").await.expect("Failed to write to cache");
@ -372,7 +378,7 @@ impl WriteOpts {
}
/// Opens the file handle for writing, returning an Writer instance.
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn open<P, K>(self, cache: P, key: K) -> Result<Writer>
where
P: AsRef<Path>,
@ -396,7 +402,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 = "tokio", feature = "smol"))]
pub async fn open_hash<P>(self, cache: P) -> Result<Writer>
where
P: AsRef<Path>,
@ -605,12 +611,14 @@ impl SyncWriter {
#[cfg(test)]
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(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn round_trip() {
let tmp = tempfile::tempdir().unwrap();
@ -620,6 +628,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();
@ -643,7 +661,7 @@ mod tests {
assert_eq!(result, original, "we did not read back what we wrote");
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_test]
async fn hash_write_async() {
let tmp = tempfile::tempdir().unwrap();
@ -659,4 +677,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");
}
}

View File

@ -13,10 +13,10 @@ use crate::index;
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let sri = cacache::write("./my-cache", "my-key", b"hello").await?;
///
@ -31,7 +31,7 @@ use crate::index;
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn remove<P, K>(cache: P, key: K) -> Result<()>
where
P: AsRef<Path>,
@ -45,10 +45,10 @@ where
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let sri = cacache::write("./my-cache", "my-key", b"hello").await?;
///
@ -64,7 +64,7 @@ where
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn remove_hash<P: AsRef<Path>>(cache: P, sri: &Integrity) -> Result<()> {
rm::rm_async(cache.as_ref(), sri).await
}
@ -74,10 +74,10 @@ pub async fn remove_hash<P: AsRef<Path>>(cache: P, sri: &Integrity) -> Result<()
///
/// ## Example
/// ```no_run
/// use async_std::prelude::*;
/// use async_attributes;
/// use macro_rules_attribute::apply;
/// use smol_macros::main;
///
/// #[async_attributes::main]
/// #[apply(main!)]
/// async fn main() -> cacache::Result<()> {
/// let sri = cacache::write("./my-cache", "my-key", b"hello").await?;
///
@ -91,7 +91,7 @@ pub async fn remove_hash<P: AsRef<Path>>(cache: P, sri: &Integrity) -> Result<()
/// Ok(())
/// }
/// ```
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(any(feature = "tokio", feature = "smol"))]
pub async fn clear<P: AsRef<Path>>(cache: P) -> Result<()> {
async fn inner(cache: &Path) -> Result<()> {
for entry in cache
@ -210,16 +210,16 @@ pub fn clear_sync<P: AsRef<Path>>(cache: P) -> Result<()> {
#[cfg(test)]
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(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "tokio")]
#[async_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();
@ -231,13 +231,27 @@ mod tests {
let data_exists = crate::exists(&dir, &sri).await;
assert!(data_exists);
});
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "smol")]
#[apply(test!)]
async fn test_remove() {
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(feature = "tokio")]
#[async_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();
@ -249,13 +263,27 @@ mod tests {
let data_exists = crate::exists(&dir, &sri).await;
assert!(!data_exists);
});
}
#[cfg(any(feature = "async-std", feature = "tokio"))]
#[cfg(feature = "smol")]
#[apply(test!)]
async fn test_remove_data() {
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(feature = "tokio")]
#[async_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();
@ -267,7 +295,22 @@ mod tests {
let data_exists = crate::exists(&dir, &sri).await;
assert!(!data_exists);
});
}
#[cfg(feature = "smol")]
#[apply(test!)]
async fn test_clear() {
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]