diff options
author | Philipp Korber <p.korber@1aim.com> | 2018-11-19 17:34:37 +0100 |
---|---|---|
committer | Philipp Korber <p.korber@1aim.com> | 2018-11-19 18:08:36 +0100 |
commit | c435424f5cb1f31711e071c660dada87b13eaaae (patch) | |
tree | 6a863104dee913161667271f7f8403093568752f | |
parent | 010e12bc6d5b8baef57fef870263be8fcb6d8a42 (diff) |
feat(resource) improved resource loading
-rw-r--r-- | core/src/context.rs | 133 | ||||
-rw-r--r-- | core/src/default_impl/fs.rs | 10 | ||||
-rw-r--r-- | core/src/lib.rs | 2 | ||||
-rw-r--r-- | core/src/mail.rs | 23 | ||||
-rw-r--r-- | core/src/resource/loading.rs | 191 | ||||
-rw-r--r-- | core/src/resource/mod.rs | 2 | ||||
-rw-r--r-- | core/tests/resource/load_file.rs | 5 |
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() } |