use super::async_stream::{OwnedRangeStream, RangeStream, ReadLimits};
use super::opener::ArchiveOpener;
use super::planning::BlockRequest;
use super::reader::{
ArchiveEntry, ArchiveFeature, ExternalBlockAccessPolicy, NoExternalBlocks, OpenOptions,
};
use super::types::ExternalLocation;
use super::view::ArchiveView;
use crate::error::PithosError;
use crate::source::{AsyncArchiveSource, SourceError};
use std::future::Future;
use std::ops::Range;
use std::sync::Arc;
use zeroize::Zeroizing;
pub trait AsyncExternalBlockResolver: Send + Sync {
fn resolve(
&self,
policy: &dyn ExternalBlockAccessPolicy,
location: &ExternalLocation,
expected_len: u64,
max_response_size: u64,
) -> impl Future<Output = Result<Vec<u8>, PithosError>> + Send;
}
impl AsyncExternalBlockResolver for NoExternalBlocks {
fn resolve(
&self,
_policy: &dyn ExternalBlockAccessPolicy,
_location: &ExternalLocation,
_expected_len: u64,
_max_response_size: u64,
) -> impl Future<Output = Result<Vec<u8>, PithosError>> + Send {
std::future::ready(Err(PithosError::UnsupportedFeature(
ArchiveFeature::ExternalStorage,
)))
}
}
pub trait BlockingHook: Send + Sync {
fn spawn_blocking<F, T>(&self, task: F) -> impl Future<Output = T> + Send
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct InlineBlocking;
impl BlockingHook for InlineBlocking {
fn spawn_blocking<F, T>(&self, task: F) -> impl Future<Output = T> + Send
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
std::future::ready(task())
}
}
pub struct AsyncArchive<S, E = NoExternalBlocks, B = InlineBlocking> {
source: S,
external: E,
external_access_policy: Option<Arc<dyn ExternalBlockAccessPolicy>>,
pub(super) hook: B,
pub(super) view: Arc<ArchiveView>,
limits: ReadLimits,
}
impl<S, E> AsyncArchive<S, E, InlineBlocking>
where
S: AsyncArchiveSource,
E: AsyncExternalBlockResolver,
{
pub async fn open(
source: S,
options: OpenOptions<E>,
known_len: Option<u64>,
) -> Result<Self, PithosError> {
Self::open_with_hook(source, options, known_len, InlineBlocking).await
}
}
impl<S, E, B> AsyncArchive<S, E, B>
where
S: AsyncArchiveSource,
E: AsyncExternalBlockResolver,
B: BlockingHook,
{
pub async fn open_with_hook(
source: S,
options: OpenOptions<E>,
known_len: Option<u64>,
hook: B,
) -> Result<Self, PithosError> {
let archive_len = match known_len {
Some(len) => len,
None => source.len().await?,
};
let (settings, external, external_access_policy) = options.into_parts();
let mut opener = ArchiveOpener::with_settings(archive_len, settings)?;
while let Some(request) = opener.request() {
let mut response = read_exact(&source, request.offset(), request.len()).await?;
let (returned, result) = hook
.spawn_blocking(move || {
let result = opener.feed(request, std::mem::take(&mut *response));
(opener, result)
})
.await;
opener = returned;
result?;
}
Ok(Self {
source,
external,
external_access_policy,
hook,
view: Arc::new(opener.finish()?),
limits: ReadLimits::default(),
})
}
pub fn with_read_limits(mut self, limits: ReadLimits) -> Self {
self.limits = limits;
self
}
pub fn metadata_digest(&self) -> [u8; 32] {
self.view.metadata_digest()
}
pub fn entries(&self) -> impl ExactSizeIterator<Item = ArchiveEntry> + '_ {
self.view.entries()
}
pub fn entry(&self, path: &str) -> Result<Option<ArchiveEntry>, PithosError> {
self.view.entry(path)
}
pub fn view(&self) -> &ArchiveView {
&self.view
}
pub fn read_range(
&self,
path: &str,
range: Range<u64>,
) -> Result<RangeStream<'_, S, E, B>, PithosError> {
let plan = self.view.plan_range(path, range)?;
Ok(RangeStream::new(
self,
plan.batches(self.limits.max_request_bytes),
self.limits,
))
}
pub fn read_range_owned(
self: Arc<Self>,
path: &str,
range: Range<u64>,
) -> Result<OwnedRangeStream<S, E, B>, PithosError>
where
S: 'static,
E: 'static,
B: 'static,
{
let cursor = self
.view
.plan_range(path, range)?
.batches(self.limits.max_request_bytes)
.detach();
let limits = self.limits;
Ok(OwnedRangeStream::new(self, cursor, limits))
}
pub(super) async fn fetch(
&self,
request: BlockRequest,
) -> Result<Zeroizing<Vec<u8>>, PithosError> {
match request {
BlockRequest::Local { offset, len } => Ok(read_exact(&self.source, offset, len).await?),
BlockRequest::External { location, len } => {
let policy = self.external_access_policy.as_deref().ok_or(
PithosError::UnsupportedFeature(ArchiveFeature::ExternalStorage),
)?;
let max_response_size = self
.view
.limits
.max_stored_block_bytes
.checked_add(4)
.ok_or_else(|| {
PithosError::ExternalBlockFraming("response policy overflow".into())
})?;
let response = self
.external
.resolve(policy, &location, len, max_response_size)
.await
.map(Zeroizing::new)?;
if response.len() as u64 != len {
return Err(PithosError::ExternalBlockFraming(
"response does not match expected size".into(),
));
}
Ok(response)
}
}
}
}
async fn read_exact<S: AsyncArchiveSource>(
source: &S,
offset: u64,
len: u64,
) -> Result<Zeroizing<Vec<u8>>, SourceError> {
let response = Zeroizing::new(source.read_at(offset, len).await?);
if response.len() as u64 != len {
return Err(SourceError::ResponseLength {
offset,
expected: len,
actual: response.len() as u64,
revision: source.revision().map(str::to_owned),
});
}
Ok(response)
}