Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: "cargo check"
run: cargo check --all --all-features

- name: "cargo test"
run: |
cargo test --all
cargo test --all --all-features
- run: cargo install cargo-all-features

- name: "cargo test all features"
run: cargo test-all-features
21 changes: 17 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,27 @@ 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 }
tokio = { version = "1.43.0", optional = true, features = ["fs", "io-util"] }
weezl = "0.1.0"

[dev-dependencies]
tiff = "0.9.1"
tokio = { version = "1.9", features = ["macros", "fs", "rt-multi-thread"] }
tokio = { version = "1.9", features = ["macros", "fs", "rt-multi-thread", "io-util"] }

[features]
default = ["object_store", "reqwest"]
tokio = ["dep:tokio"]
reqwest = ["dep:reqwest"]
object_store = ["dep:object_store"]

[package.metadata.cargo-all-features]
# If your crate has a large number of optional dependencies, skip them for speed
# skip_optional_dependencies = true

# Exclude certain features from the build matrix
denylist = ["default"]
1 change: 1 addition & 0 deletions src/cog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ impl TIFF {
}
}

#[cfg(feature = "object_store")]
#[cfg(test)]
mod test {
use std::io::BufReader;
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand All @@ -32,6 +33,7 @@ pub enum AsyncTiffError {
InternalTIFFError(#[from] crate::tiff::TiffError),

/// Reqwest error
#[cfg(feature = "reqwest")]
#[error(transparent)]
ReqwestError(#[from] reqwest::Error),

Expand Down
58 changes: 31 additions & 27 deletions src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use std::sync::Arc;
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
use bytes::buf::Reader;
use bytes::{Buf, Bytes};
use futures::future::{BoxFuture, FutureExt, TryFutureExt};
use futures::future::{BoxFuture, FutureExt};
#[cfg(feature = "object_store")]
use futures::TryFutureExt;
#[cfg(feature = "object_store")]
use object_store::ObjectStore;

use crate::error::{AsyncTiffError, AsyncTiffResult};
Expand Down Expand Up @@ -67,36 +70,36 @@ impl AsyncFileReader for Box<dyn AsyncFileReader + '_> {
}
}

// #[cfg(feature = "tokio")]
// impl<T: tokio::io::AsyncRead + tokio::io::AsyncSeek + Unpin + Debug + Send + Sync> AsyncFileReader
// for T
// {
// fn get_bytes(&self, range: Range<u64>) -> BoxFuture<'_, AsyncTiffResult<Bytes>> {
// use tokio::io::{AsyncReadExt, AsyncSeekExt};

// async move {
// self.seek(std::io::SeekFrom::Start(range.start)).await?;

// let to_read = (range.end - range.start).try_into().unwrap();
// let mut buffer = Vec::with_capacity(to_read);
// let read = self.take(to_read as u64).read_to_end(&mut buffer).await?;
// if read != to_read {
// return Err(AsyncTiffError::EndOfFile(to_read, read));
// }

// Ok(buffer.into())
// }
// .boxed()
// }
// }
#[cfg(feature = "tokio")]
impl AsyncFileReader for tokio::fs::File {
fn get_bytes(&self, range: Range<u64>) -> BoxFuture<'_, AsyncTiffResult<Bytes>> {
use tokio::io::{AsyncReadExt, AsyncSeekExt};

async move {
let mut file = (*self).try_clone().await?;
file.seek(std::io::SeekFrom::Start(range.start)).await?;

let to_read = (range.end - range.start).try_into().unwrap();
let mut buffer = Vec::with_capacity(to_read);
let read = file.take(to_read as u64).read_to_end(&mut buffer).await?;
if read != to_read {
return Err(AsyncTiffError::EndOfFile(to_read, read));
}

Ok(buffer.into())
}
.boxed()
}
}

/// An AsyncFileReader that reads from an [`ObjectStore`] instance.
#[cfg(feature = "object_store")]
#[derive(Clone, Debug)]
pub struct ObjectReader {
store: Arc<dyn ObjectStore>,
path: object_store::path::Path,
}

#[cfg(feature = "object_store")]
impl ObjectReader {
/// Creates a new [`ObjectReader`] for the provided [`ObjectStore`] and path
///
Expand All @@ -105,7 +108,7 @@ impl ObjectReader {
Self { store, path }
}
}

#[cfg(feature = "object_store")]
impl AsyncFileReader for ObjectReader {
fn get_bytes(&self, range: Range<u64>) -> BoxFuture<'_, AsyncTiffResult<Bytes>> {
let range = range.start as _..range.end as _;
Expand Down Expand Up @@ -134,19 +137,20 @@ impl AsyncFileReader for ObjectReader {
}

/// 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")]
impl AsyncFileReader for ReqwestReader {
fn get_bytes(&self, range: Range<u64>) -> BoxFuture<'_, AsyncTiffResult<Bytes>> {
let url = self.url.clone();
Expand Down
67 changes: 65 additions & 2 deletions tests/image_tiff/util.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,78 @@
use std::env::current_dir;
use async_tiff::TIFF;
use std::sync::Arc;

#[cfg(feature = "object_store")]
use async_tiff::reader::ObjectReader;
use async_tiff::TIFF;
#[cfg(feature = "object_store")]
use object_store::local::LocalFileSystem;
#[cfg(feature = "object_store")]
use std::env::current_dir;

#[cfg(not(any(feature = "tokio", feature = "object_store")))]
use async_tiff::{
error::{AsyncTiffError, AsyncTiffResult},
reader::AsyncFileReader,
};
#[cfg(not(any(feature = "tokio", feature = "object_store")))]
use bytes::Bytes;
#[cfg(not(any(feature = "tokio", feature = "object_store")))]
use futures::{future::BoxFuture, FutureExt};
#[cfg(not(any(feature = "tokio", feature = "object_store")))]
use std::ops::Range;

const TEST_IMAGE_DIR: &str = "tests/image_tiff/images/";

#[cfg(feature = "object_store")]
pub(crate) async fn open_tiff(filename: &str) -> TIFF {
let store = Arc::new(LocalFileSystem::new_with_prefix(current_dir().unwrap()).unwrap());
let path = format!("{TEST_IMAGE_DIR}/{filename}");
let reader = Arc::new(ObjectReader::new(store.clone(), path.as_str().into()));
TIFF::try_open(reader).await.unwrap()
}

#[cfg(all(feature = "tokio", not(feature = "object_store")))]
pub(crate) async fn open_tiff(filename: &str) -> TIFF {
// let store = Arc::new(LocalFileSystem::new_with_prefix(current_dir().unwrap()).unwrap());
let path = format!("{TEST_IMAGE_DIR}/{filename}");
let reader = Arc::new(
tokio::fs::File::open(path)
.await
.expect("could not open file"),
);
TIFF::try_open(reader).await.unwrap()
}

#[cfg(not(any(feature = "tokio", feature = "object_store")))]
#[derive(Debug)]
struct TokioFile(tokio::fs::File);
#[cfg(not(any(feature = "tokio", feature = "object_store")))]
impl AsyncFileReader for TokioFile {
fn get_bytes(&self, range: Range<u64>) -> BoxFuture<'_, AsyncTiffResult<Bytes>> {
use tokio::io::{AsyncReadExt, AsyncSeekExt};

async move {
let mut file = self.0.try_clone().await?;
file.seek(std::io::SeekFrom::Start(range.start)).await?;

let to_read = (range.end - range.start).try_into().unwrap();
let mut buffer = Vec::with_capacity(to_read);
let read = file.take(to_read as u64).read_to_end(&mut buffer).await?;
if read != to_read {
return Err(AsyncTiffError::EndOfFile(to_read, read));
}

Ok(buffer.into())
}
.boxed()
}
}
#[cfg(not(any(feature = "tokio", feature = "object_store")))]
pub(crate) async fn open_tiff(filename: &str) -> TIFF {
let path = format!("{TEST_IMAGE_DIR}/{filename}");
let reader = Arc::new(TokioFile(
tokio::fs::File::open(path)
.await
.expect("could not open file"),
));
TIFF::try_open(reader).await.unwrap()
}