summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Korber <p.korber@1aim.com>2018-11-19 17:34:37 +0100
committerPhilipp Korber <p.korber@1aim.com>2018-11-19 18:08:36 +0100
commitc435424f5cb1f31711e071c660dada87b13eaaae (patch)
tree6a863104dee913161667271f7f8403093568752f
parent010e12bc6d5b8baef57fef870263be8fcb6d8a42 (diff)
feat(resource) improved resource loading
-rw-r--r--core/src/context.rs133
-rw-r--r--core/src/default_impl/fs.rs10
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/mail.rs23
-rw-r--r--core/src/resource/loading.rs191
-rw-r--r--core/src/resource/mod.rs2
-rw-r--r--core/tests/resource/load_file.rs5
7 files changed, 312 insertions, 54 deletions
diff --git a/core/src/context.rs b/core/src/context.rs
index 1077a96..88f1045 100644
--- a/core/src/context.rs
+++ b/core/src/context.rs
@@ -2,15 +2,46 @@
use std::sync::Arc;
use std::fmt::Debug;
-use futures::{ future, Future, IntoFuture };
+use futures::{ future::{self, Either}, Future, IntoFuture };
use utils::SendBoxFuture;
use headers::header_components::{
MessageId, ContentId
};
-use ::error::ResourceLoadingError;
-use ::resource::{Source, Data, EncData};
+use crate::{
+ resource::{Source, Data, EncData, Resource},
+ error::ResourceLoadingError
+};
+
+
+/// Represents Data which might already have been transfer encoded.
+pub enum MaybeEncData {
+ /// The data is returned normally.
+ Data(Data),
+
+ /// The data is returned in a already transfer encoded variant.
+ EncData(EncData)
+}
+
+impl MaybeEncData {
+
+ pub fn to_resource(self) -> Resource {
+ match self {
+ MaybeEncData::Data(data) => Resource::Data(data),
+ MaybeEncData::EncData(enc_data) => Resource::EncData(enc_data)
+ }
+ }
+
+ pub fn encode(self, ctx: &impl Context)
+ -> impl Future<Item=EncData,Error=ResourceLoadingError>
+ {
+ match self {
+ MaybeEncData::Data(data) => Either::A(ctx.load_transfer_encoded_resource(&Resource::Data(data))),
+ MaybeEncData::EncData(enc) => Either::B(future::ok(enc))
+ }
+ }
+}
/// This library needs a context for creating/encoding mails.
///
@@ -57,35 +88,46 @@ pub trait Context: Debug + Clone + Send + Sync + 'static {
/// encoded instance skipping the loading + encoding step for
/// common resources like e.g. a logo.
///
+ /// If the context implementation only caches enc data instances
+ /// but not data instances it might return [`MaybeEncData::EncData`].
+ /// If it newly load the resource it _should_ return a [`MaybeEncData::Data`]
+ /// variant.
+ ///
/// # Async Considerations
///
/// This function should not block and schedule the encoding
/// in some other place e.g. by using the contexts offload
/// functionality.
fn load_resource(&self, source: &Source)
- -> SendBoxFuture<EncData, ResourceLoadingError>;
+ -> SendBoxFuture<MaybeEncData, ResourceLoadingError>;
- /// Transfer encodes a `Data` instance.
+ /// Loads and Transfer encodes a `Resource` instance.
///
/// This is called when a `Mail` instance is converted into
- /// a encodable mail an a `Resource::Data` instance is found.
+ /// a encodable mail.
+ ///
+ /// The default impl. of this function just:
+ ///
+ /// 1. calls load_resource and chains a "offloaded" transfer encoding
+ /// if a `Resource::Source` is found.
+ /// 2. transfer encodes the data "offloaded" if `Resource::Data` is found
+ /// 3. just returns the encoded data if `Resource::EncData` is found
///
- /// The default impl. of this function just calls
- /// `data.transfer_encode(data, Default::default())` but a more
- /// sophisticated implementation could use the `Data`s content id
- /// for some caching scheme e.g. a LRU cache. Which can safe the
- /// encoding step for commonly used resources like e.g. a logo.
+ /// A more advances implementation could for example integrate
+ /// a LRU cache.
+ ///
+ /// The default impl is available as the `default_impl_for_load_transfer_encoded_resource`
+ /// function.
///
/// # Async Considerations
///
/// This function should not block and schedule the encoding
/// in some other place e.g. by using the contexts offload
/// functionality.
- fn transfer_encode_resource(&self, data: &Data)
+ fn load_transfer_encoded_resource(&self, resource: &Resource)
-> SendBoxFuture<EncData, ResourceLoadingError>
{
- let data = data.clone();
- self.offload_fn(move || Ok(data.transfer_encode(Default::default())))
+ default_impl_for_load_transfer_encoded_resource(self, resource)
}
/// generate a unique content id
@@ -137,25 +179,64 @@ pub trait Context: Debug + Clone + Send + Sync + 'static {
}
+/// Provides the default impl for the `load_transfer_encoded_resource` method of `Context`.
+///
+/// This function guarantees to only call `load_resource` and `offload`/`offload_fn` on the
+/// passed in context, to prevent infinite recursion.
+pub fn default_impl_for_load_transfer_encoded_resource(ctx: &impl Context, resource: &Resource)
+ -> SendBoxFuture<EncData, ResourceLoadingError>
+{
+ match resource {
+ Resource::Source(source) => {
+ let ctx2 = ctx.clone();
+ let fut = ctx.load_resource(&source)
+ .and_then(move |me_data| {
+ match me_data {
+ MaybeEncData::Data(data) => {
+ Either::A(ctx2.offload_fn(move || Ok(data.transfer_encode(Default::default()))))
+ },
+ MaybeEncData::EncData(enc_data) => {
+ Either::B(future::ok(enc_data))
+ }
+ }
+ });
+ Box::new(fut)
+ },
+ Resource::Data(data) => {
+ let data = data.clone();
+ ctx.offload_fn(move || Ok(data.transfer_encode(Default::default())))
+ },
+ Resource::EncData(enc_data) => {
+ Box::new(future::ok(enc_data.clone()))
+ }
+ }
+}
+
/// Trait needed to be implemented for providing the resource loading parts to a`CompositeContext`.
pub trait ResourceLoaderComponent: Debug + Send + Sync + 'static {
/// Calls to `Context::load_resource` will be forwarded to this method.
///
/// It is the same as `Context::load_resource` except that a reference
- /// to an `OffloaderComponent` will be passed in as it's likely needed.
+ /// to the context containing this component is passed in. To prevent
+ /// infinite recursion the `Context.load_resource` method _must not_
+ /// be called. Additionally the `Context.load_transfer_encoded_resource` _must not_
+ /// be called if it uses `Context.load_resource`.
fn load_resource(&self, source: &Source, ctx: &impl Context)
- -> SendBoxFuture<EncData, ResourceLoadingError>;
+ -> SendBoxFuture<MaybeEncData, ResourceLoadingError>;
/// Calls to `Context::transfer_encode_resource` will be forwarded to this method.
///
/// It is the same as `Context::transfer_encode_resource` except that a reference
- /// to an `OffloaderComponent` will be passed in as it's likely needed.
- fn transfer_encode_resource(&self, data: &Data, ctx: &impl Context)
+ /// to the context containing this component is passed in to make the `offload`
+ /// and `load_resource` methods of `Context` available.
+ ///
+ /// To prevent infinite recursion the `load_transfer_encoded_resource` method
+ /// of the context _must not_ be called.
+ fn load_transfer_encoded_resource(&self, resource: &Resource, ctx: &impl Context)
-> SendBoxFuture<EncData, ResourceLoadingError>
{
- let data = data.clone();
- ctx.offload_fn(move || Ok(data.transfer_encode(Default::default())))
+ default_impl_for_load_transfer_encoded_resource(ctx, resource)
}
}
@@ -254,15 +335,15 @@ impl<R, O, M> Context for CompositeContext<R, O, M>
{
fn load_resource(&self, source: &Source)
- -> SendBoxFuture<EncData, ResourceLoadingError>
+ -> SendBoxFuture<MaybeEncData, ResourceLoadingError>
{
self.resource_loader().load_resource(source, self)
}
- fn transfer_encode_resource(&self, data: &Data)
+ fn load_transfer_encoded_resource(&self, resource: &Resource)
-> SendBoxFuture<EncData, ResourceLoadingError>
{
- self.resource_loader().transfer_encode_resource(data, self)
+ self.resource_loader().load_transfer_encoded_resource(resource, self)
}
fn offload<F>(&self, fut: F) -> SendBoxFuture<F::Item, F::Error>
@@ -315,14 +396,14 @@ impl<C> ResourceLoaderComponent for C
{
fn load_resource(&self, source: &Source, _: &impl Context)
- -> SendBoxFuture<EncData, ResourceLoadingError>
+ -> SendBoxFuture<MaybeEncData, ResourceLoadingError>
{
<Self as Context>::load_resource(self, source)
}
- fn transfer_encode_resource(&self, data: &Data, _: &impl Context)
+ fn load_transfer_encoded_resource(&self, resource: &Resource, _: &impl Context)
-> SendBoxFuture<EncData, ResourceLoadingError>
{
- <Self as Context>::transfer_encode_resource(self, data)
+ <Self as Context>::load_transfer_encoded_resource(self, resource)
}
} \ No newline at end of file
diff --git a/core/src/default_impl/fs.rs b/core/src/default_impl/fs.rs
index e60b0ad..bab8af7 100644
--- a/core/src/default_impl/fs.rs
+++ b/core/src/default_impl/fs.rs
@@ -15,7 +15,7 @@ use headers::header_components::{
FileMeta
};
-use ::{
+use crate::{
iri::IRI,
utils::{
SendBoxFuture,
@@ -27,14 +27,14 @@ use ::{
},
resource:: {
Data,
- EncData,
Source,
UseMediaType,
Metadata
},
context::{
Context,
- ResourceLoaderComponent
+ ResourceLoaderComponent,
+ MaybeEncData
}
};
@@ -100,7 +100,7 @@ impl<ValidateScheme> ResourceLoaderComponent for FsResourceLoader<ValidateScheme
{
fn load_resource(&self, source: &Source, ctx: &impl Context)
- -> SendBoxFuture<EncData, ResourceLoadingError>
+ -> SendBoxFuture<MaybeEncData, ResourceLoadingError>
{
if ValidateScheme::ENABLED && !self.iri_has_compatible_scheme(&source.iri) {
let err = ResourceLoadingError
@@ -119,7 +119,7 @@ impl<ValidateScheme> ResourceLoaderComponent for FsResourceLoader<ValidateScheme
use_media_type,
use_file_name,
ctx,
- |data| Ok(data.transfer_encode(Default::default()))
+ |data| Ok(MaybeEncData::EncData(data.transfer_encode(Default::default())))
)
}
}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index f1b1ba7..0ce4bbc 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -54,7 +54,7 @@ pub use self::iri::IRI;
pub use self::resource::*;
pub use self::mail::*;
-pub use ::context::Context;
+pub use ::context::{Context, MaybeEncData};
diff --git a/core/src/mail.rs b/core/src/mail.rs
index 0329a4c..4c2e130 100644
--- a/core/src/mail.rs
+++ b/core/src/mail.rs
@@ -10,10 +10,7 @@ use std::{
use soft_ascii_string::SoftAsciiString;
use futures::{
- future::{
- self,
- Either
- },
+ future,
Future,
Async,
Poll
@@ -430,10 +427,7 @@ enum InnerMailFuture<C: Context> {
New { mail: Mail, ctx: C },
Loading {
mail: Mail,
- pending: future::JoinAll<Vec<Either<
- SendBoxFuture<EncData, ResourceLoadingError>,
- future::FutureResult<EncData, ResourceLoadingError>
- >>>,
+ pending: future::JoinAll<Vec<SendBoxFuture<EncData, ResourceLoadingError>>>,
ctx: C
},
Poison
@@ -464,18 +458,7 @@ impl<T> Future for MailFuture<T>
let mut futures = Vec::new();
mail.visit_mail_bodies(&mut |resource: &Resource| {
- let fut = match resource {
- &Resource::Source(ref source) => {
- Either::A(ctx.load_resource(source))
- },
- &Resource::Data(ref data) => {
- Either::A(ctx.transfer_encode_resource(data))
- },
- &Resource::EncData(ref enc_data) => {
- Either::B(future::ok(enc_data.clone()))
- }
- };
-
+ let fut = ctx.load_transfer_encoded_resource(resource);
futures.push(fut);
});
diff --git a/core/src/resource/loading.rs b/core/src/resource/loading.rs
new file mode 100644
index 0000000..97c840d
--- /dev/null
+++ b/core/src/resource/loading.rs
@@ -0,0 +1,191 @@
+//! This modules add some helpers to load all resources in some data type.
+use std::{
+ borrow::{ToOwned, Borrow},
+ collections::HashMap,
+ mem
+};
+
+use futures::{
+ Future, Poll, Async,
+ try_ready,
+ future::{self, JoinAll}
+};
+
+use crate::{
+ Context, MaybeEncData, Resource,
+ utils::SendBoxFuture,
+ error::ResourceLoadingError
+};
+
+pub trait ContainedResourcesAccess {
+ type Key: ToOwned + ?Sized;
+
+ /// Visit all resources.
+ ///
+ /// This method is not allowed to be implemented in a way that
+ /// it might visit resources, which are not accessable with
+ /// [`Self.access_resource_mut()`]. This means that without
+ /// e.g. a `RwLock` this can not visit resources in a `Arc`.
+ fn visit_resources(&self, visitor: &mut impl FnMut(&Self::Key, &Resource));
+
+ /// Return a mut ref for a resource based on given key.
+ ///
+ /// If a resource is visited in a `Self.visit_resources()` call
+ /// and the state of self was not changes this method has to
+ /// be able to return a mut reference to it.
+ ///
+ /// To allow accessing resources in a mutext this does pass
+ /// the mut ref to a closure instead of returning it.
+ ///
+ fn access_resource_mut<R>(
+ &mut self,
+ key: &Self::Key,
+ modify: impl FnOnce(Option<&mut Resource>) -> R
+ ) -> R;
+
+ /// Return a ref for a resource base on given key.
+ fn access_resource<R>(
+ &self,
+ key: &Self::Key,
+ modify: impl FnOnce(Option<&Resource>) -> R
+ ) -> R;
+}
+
+
+impl ContainedResourcesAccess for Vec<Resource> {
+ type Key = usize;
+
+ fn visit_resources(&self, visitor: &mut impl FnMut(&Self::Key, &Resource)) {
+ for (idx, resource) in self.iter().enumerate() {
+ visitor(&idx, resource)
+ }
+ }
+
+ fn access_resource_mut<R>(
+ &mut self,
+ key: &Self::Key,
+ modify: impl FnOnce(Option<&mut Resource>) -> R
+ ) -> R {
+ modify(self.get_mut(*key))
+ }
+
+ /// Return a ref for a resource base on given key.
+ fn access_resource<R>(
+ &self,
+ key: &Self::Key,
+ modify: impl FnOnce(Option<&Resource>) -> R
+ ) -> R {
+ modify(self.get(*key))
+ }
+}
+
+impl ContainedResourcesAccess for HashMap<String, Resource> {
+ type Key = str;
+
+ fn visit_resources(&self, visitor: &mut impl FnMut(&Self::Key, &Resource)) {
+ for (key, resource) in self.iter() {
+ visitor(&key, resource)
+ }
+ }
+
+ fn access_resource_mut<R>(
+ &mut self,
+ key: &Self::Key,
+ modify: impl FnOnce(Option<&mut Resource>) -> R
+ ) -> R {
+ modify(self.get_mut(key))
+ }
+
+ /// Return a ref for a resource base on given key.
+ fn access_resource<R>(
+ &self,
+ key: &Self::Key,
+ modify: impl FnOnce(Option<&Resource>) -> R
+ ) -> R {
+ modify(self.get(key))
+ }
+}
+
+//TODO[feat] impl. where applicable in std (Box, BTreeMap, other HashMap, etc.)
+
+
+impl Resource {
+
+ pub fn load_container<CO>(container: CO, ctx: &impl Context)
+ -> ResourceContainerLoadingFuture<CO>
+ where CO: ContainedResourcesAccess
+ {
+ ResourceContainerLoadingFuture::start_loading(container, ctx)
+ }
+}
+
+pub struct ResourceContainerLoadingFuture<C>
+ where C: ContainedResourcesAccess
+{
+ inner: Option<InnerFuture<C>>
+}
+
+struct InnerFuture<C>
+ where C: ContainedResourcesAccess
+{
+ container: C,
+ keys: Vec<<C::Key as ToOwned>::Owned>,
+ futs: JoinAll<Vec<SendBoxFuture<MaybeEncData, ResourceLoadingError>>>
+}
+
+impl<CO> ResourceContainerLoadingFuture<CO>
+ where CO: ContainedResourcesAccess
+{
+ pub fn start_loading(container: CO, ctx: &impl Context) -> Self {
+ let mut keys = Vec::new();
+ let mut futs = Vec::new();
+
+ container.visit_resources(&mut |key, resource| {
+ if let &Resource::Source(ref source) = resource {
+ let fut = ctx.load_resource(source);
+ futs.push(fut);
+ keys.push(key.to_owned());
+ }
+ });
+
+ let futs = future::join_all(futs);
+
+ ResourceContainerLoadingFuture {
+ inner: Some(InnerFuture {
+ container,
+ keys,
+ futs
+ }),
+ }
+ }
+}
+
+impl<C> Future for ResourceContainerLoadingFuture<C>
+ where C: ContainedResourcesAccess
+{
+ type Item = C;
+ type Error = ResourceLoadingError;
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ let loaded;
+
+ if let Some(loading) = self.inner.as_mut().map(|inner| &mut inner.futs) {
+ loaded = try_ready!(loading.poll());
+ } else {
+ panic!("future called after it resolved");
+ };
+
+ //UNWRAP_SAFE: can only be reached if it was some
+ let InnerFuture { mut container, keys, futs:_ } = self.inner.take().unwrap();
+
+ for (key, new_resource) in keys.into_iter().zip(loaded.into_iter()) {
+ container.access_resource_mut(key.borrow(), |resource_ref| {
+ if let Some(resource_ref) = resource_ref {
+ mem::replace(resource_ref, new_resource.to_resource());
+ }
+ })
+ }
+
+ Ok(Async::Ready(container))
+ }
+}
diff --git a/core/src/resource/mod.rs b/core/src/resource/mod.rs
index fe0a757..0070f24 100644
--- a/core/src/resource/mod.rs
+++ b/core/src/resource/mod.rs
@@ -117,9 +117,11 @@ Source->| | |
mod source;
mod data;
+mod loading;
pub use self::source::*;
pub use self::data::*;
+pub use self::loading::*;
diff --git a/core/tests/resource/load_file.rs b/core/tests/resource/load_file.rs
index a1b5407..48a2e67 100644
--- a/core/tests/resource/load_file.rs
+++ b/core/tests/resource/load_file.rs
@@ -9,7 +9,8 @@ use mail_core::{
UseMediaType,
IRI,
Source,
- context::Context,
+ Context,
+ Resource
};
use mail_core::context::CompositeContext;
use mail_core::default_impl::{FsResourceLoader, simple_cpu_pool, HashedIdGen, simple_context};
@@ -35,7 +36,7 @@ fn loaded_resource(path: &str, media_type: &str, name: Option<&str>) -> EncData
use_file_name: name.map(|s|s.to_owned()),
};
- ctx.load_resource(&source).wait().unwrap()
+ ctx.load_transfer_encoded_resource(&Resource::Source(source)).wait().unwrap()
}