diff --git a/Cargo.toml b/Cargo.toml index 1c4ed93..ee6a936 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,16 +9,17 @@ description = "Low-level asynchronous TIFF reader." readme = "README.md" [dependencies] +async-trait = "0.1.88" byteorder = "1" bytes = "1.7.0" flate2 = "1.0.20" futures = "0.3.31" jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false } num_enum = "0.7.3" -object_store = "0.12" +object_store = { version = "0.12", optional = true } # In the future we could make this feature-flagged, but for now we depend on # object_store which uses reqwest. -reqwest = { version = "0.12", default-features = false } +reqwest = { version = "0.12", default-features = false, optional = true } thiserror = "1" tokio = { version = "1.43.0", optional = true } weezl = "0.1.0" @@ -26,3 +27,8 @@ weezl = "0.1.0" [dev-dependencies] tiff = "0.9.1" tokio = { version = "1.9", features = ["macros", "fs", "rt-multi-thread"] } + +[features] +default = ["object_store"] +reqwest = ["dep:reqwest"] +object_store = ["dep:object_store", "reqwest"] diff --git a/src/cog.rs b/src/cog.rs index e9e3c5b..69a5d98 100644 --- a/src/cog.rs +++ b/src/cog.rs @@ -56,6 +56,7 @@ impl TIFF { } } +#[cfg(feature = "object_store")] #[cfg(test)] mod test { use std::io::BufReader; diff --git a/src/error.rs b/src/error.rs index 9f5b7f0..720f796 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,6 +24,7 @@ pub enum AsyncTiffError { JPEGDecodingError(#[from] jpeg::Error), /// Error while fetching data using object store. + #[cfg(feature = "object_store")] #[error(transparent)] ObjectStore(#[from] object_store::Error), @@ -32,6 +33,7 @@ pub enum AsyncTiffError { InternalTIFFError(#[from] crate::tiff::TiffError), /// Reqwest error + #[cfg(feature = "reqwest")] // see https://www.reddit.com/r/rust/comments/xyik51/comment/irhei39/ #[error(transparent)] ReqwestError(#[from] reqwest::Error), diff --git a/src/reader.rs b/src/reader.rs index 8563077..16c7d15 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -5,10 +5,12 @@ use std::io::Read; use std::ops::Range; use std::sync::Arc; +use async_trait::async_trait; use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; use bytes::buf::Reader; use bytes::{Buf, Bytes}; -use futures::future::{BoxFuture, FutureExt, TryFutureExt}; +use futures::future::TryFutureExt; +#[cfg(feature = "object_store")] use object_store::ObjectStore; use crate::error::{AsyncTiffError, AsyncTiffResult}; @@ -29,41 +31,36 @@ use crate::error::{AsyncTiffError, AsyncTiffResult}; /// [`ObjectStore`]: object_store::ObjectStore /// /// [`tokio::fs::File`]: https://docs.rs/tokio/latest/tokio/fs/struct.File.html +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait AsyncFileReader: Debug + Send + Sync { /// Retrieve the bytes in `range` - fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult>; + async fn get_bytes(&self, range: Range) -> AsyncTiffResult; /// Retrieve multiple byte ranges. The default implementation will call `get_bytes` /// sequentially - fn get_byte_ranges( - &self, - ranges: Vec>, - ) -> BoxFuture<'_, AsyncTiffResult>> { - async move { - let mut result = Vec::with_capacity(ranges.len()); - - for range in ranges.into_iter() { - let data = self.get_bytes(range).await?; - result.push(data); - } + async fn get_byte_ranges(&self, ranges: Vec>) -> AsyncTiffResult> { + let mut result = Vec::with_capacity(ranges.len()); - Ok(result) + for range in ranges.into_iter() { + let data = self.get_bytes(range).await?; + result.push(data); } - .boxed() + + Ok(result) } } /// This allows Box to be used as an AsyncFileReader, -impl AsyncFileReader for Box { - fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { - self.as_ref().get_bytes(range) +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl AsyncFileReader for Box { + async fn get_bytes(&self, range: Range) -> AsyncTiffResult { + self.get_bytes(range).await } - fn get_byte_ranges( - &self, - ranges: Vec>, - ) -> BoxFuture<'_, AsyncTiffResult>> { - self.as_ref().get_byte_ranges(ranges) + async fn get_byte_ranges(&self, ranges: Vec>) -> AsyncTiffResult> { + self.get_byte_ranges(ranges).await } } @@ -91,12 +88,13 @@ impl AsyncFileReader for Box { // } /// An AsyncFileReader that reads from an [`ObjectStore`] instance. +#[cfg(feature = "object_store")] #[derive(Clone, Debug)] pub struct ObjectReader { store: Arc, path: object_store::path::Path, } - +#[cfg(feature = "object_store")] impl ObjectReader { /// Creates a new [`ObjectReader`] for the provided [`ObjectStore`] and path /// @@ -105,17 +103,19 @@ impl ObjectReader { Self { store, path } } } - +#[cfg(feature = "object_store")] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl AsyncFileReader for ObjectReader { - fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { + async fn get_bytes(&self, range: Range) -> AsyncTiffResult { let range = range.start as _..range.end as _; self.store .get_range(&self.path, range) .map_err(|e| e.into()) - .boxed() + .await } - fn get_byte_ranges(&self, ranges: Vec>) -> BoxFuture<'_, AsyncTiffResult>> + async fn get_byte_ranges(&self, ranges: Vec>) -> AsyncTiffResult> where Self: Send, { @@ -123,42 +123,42 @@ impl AsyncFileReader for ObjectReader { .into_iter() .map(|r| r.start as _..r.end as _) .collect::>(); - async move { - self.store - .get_ranges(&self.path, &ranges) - .await - .map_err(|e| e.into()) - } - .boxed() + self.store + .get_ranges(&self.path, &ranges) + .await + .map_err(|e| e.into()) } } /// An AsyncFileReader that reads from a URL using reqwest. +#[cfg(feature = "reqwest")] #[derive(Debug, Clone)] pub struct ReqwestReader { client: reqwest::Client, url: reqwest::Url, } - +#[cfg(feature = "reqwest")] impl ReqwestReader { /// Construct a new ReqwestReader from a reqwest client and URL. pub fn new(client: reqwest::Client, url: reqwest::Url) -> Self { Self { client, url } } } - +#[cfg(feature = "reqwest")] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl AsyncFileReader for ReqwestReader { - fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { + async fn get_bytes(&self, range: Range) -> AsyncTiffResult { + // } + // fn get_bytes<'async_trait>(&'async_trait self, range: Range) -> BoxFuture<'async_trait, AsyncTiffResult> + // { let url = self.url.clone(); let client = self.client.clone(); // HTTP range is inclusive, so we need to subtract 1 from the end let range = format!("bytes={}-{}", range.start, range.end - 1); - async move { - let response = client.get(url).header("Range", range).send().await?; - let bytes = response.bytes().await?; - Ok(bytes) - } - .boxed() + let response = client.get(url).header("Range", range).send().await?; + let bytes = response.bytes().await?; + Ok(bytes) } } @@ -177,29 +177,31 @@ impl PrefetchReader { } } +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl AsyncFileReader for PrefetchReader { - fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { + async fn get_bytes(&self, range: Range) -> AsyncTiffResult { if range.start < self.buffer.len() as _ { if range.end < self.buffer.len() as _ { let usize_range = range.start as usize..range.end as usize; let result = self.buffer.slice(usize_range); - async { Ok(result) }.boxed() + Ok(result) } else { // TODO: reuse partial internal buffer - self.reader.get_bytes(range) + self.reader.get_bytes(range).await } } else { - self.reader.get_bytes(range) + self.reader.get_bytes(range).await } } - fn get_byte_ranges(&self, ranges: Vec>) -> BoxFuture<'_, AsyncTiffResult>> + async fn get_byte_ranges(&self, ranges: Vec>) -> AsyncTiffResult> where Self: Send, { // In practice, get_byte_ranges is only used for fetching tiles, which are unlikely to // overlap a metadata prefetch. - self.reader.get_byte_ranges(ranges) + self.reader.get_byte_ranges(ranges).await } }