use crate::cache::stateful_template_loader::StatefulTemplateLoader;
use crate::cache::{TemplateLoader, TemplateSource};
use crate::error::{Result, TemplateError};
use std::sync::Arc;
pub struct MultiLoader {
loaders: Vec<Arc<dyn TemplateLoader>>,
}
impl MultiLoader {
pub fn new(loaders: Vec<Arc<dyn TemplateLoader>>) -> Self {
MultiLoader { loaders }
}
pub fn len(&self) -> usize {
self.loaders.len()
}
pub fn is_empty(&self) -> bool {
self.loaders.is_empty()
}
}
impl TemplateLoader for MultiLoader {
fn find(&self, name: &str) -> Result<Option<Box<dyn TemplateSource>>> {
for loader in &self.loaders {
if let Some(source) = loader.find(name)? {
return Ok(Some(Box::new(MultiSource {
inner: source,
loader: loader.clone(),
})));
}
}
Ok(None)
}
fn read(&self, src: &dyn TemplateSource) -> Result<String> {
let multi = downcast_multi_src(src)?;
multi.loader.read(&*multi.inner)
}
fn read_encoded(&self, src: &dyn TemplateSource, encoding: &str) -> Result<String> {
let multi = downcast_multi_src(src)?;
multi.loader.read_encoded(&*multi.inner, encoding)
}
fn last_modified(&self, src: &dyn TemplateSource) -> Result<i64> {
let multi = downcast_multi_src(src)?;
multi.loader.last_modified(&*multi.inner)
}
fn as_stateful(&self) -> Option<&dyn StatefulTemplateLoader> {
Some(self)
}
}
impl StatefulTemplateLoader for MultiLoader {
fn reset_state(&self) {
for loader in &self.loaders {
if let Some(sl) = loader.as_stateful() {
sl.reset_state();
}
}
}
}
pub struct MultiSource {
inner: Box<dyn TemplateSource>,
loader: Arc<dyn TemplateLoader>,
}
impl TemplateSource for MultiSource {
fn name(&self) -> String {
self.inner.name()
}
fn as_any(&self) -> Option<&dyn std::any::Any> {
Some(self)
}
}
impl MultiSource {
fn downcast(src: &dyn TemplateSource) -> Option<&MultiSource> {
src.as_any().and_then(|a| a.downcast_ref::<MultiSource>())
}
}
fn downcast_multi_src(src: &dyn TemplateSource) -> Result<&MultiSource> {
MultiSource::downcast(src).ok_or_else(|| {
TemplateError::misc(
"Not a MultiSource: template source was created by a different TemplateLoader",
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cache::StringLoader;
use std::sync::atomic::{AtomicUsize, Ordering};
struct CountingLoader {
finds: Arc<AtomicUsize>,
inner: Arc<StringLoader>,
}
impl CountingLoader {
fn new(finds: Arc<AtomicUsize>) -> Self {
CountingLoader {
finds,
inner: Arc::new(StringLoader::default()),
}
}
}
impl TemplateLoader for CountingLoader {
fn find(&self, name: &str) -> Result<Option<Box<dyn TemplateSource>>> {
self.finds.fetch_add(1, Ordering::SeqCst);
self.inner.find(name)
}
fn read(&self, src: &dyn TemplateSource) -> Result<String> {
self.inner.read(src)
}
fn last_modified(&self, src: &dyn TemplateSource) -> Result<i64> {
self.inner.last_modified(src)
}
}
#[test]
fn first_hit_wins_second_not_queried() {
let finds1 = Arc::new(AtomicUsize::new(0));
let finds2 = Arc::new(AtomicUsize::new(0));
let l1 = CountingLoader::new(finds1.clone());
l1.inner.put("a.ftl", "from-first");
let l2 = CountingLoader::new(finds2.clone());
l2.inner.put("a.ftl", "from-second");
let multi = MultiLoader::new(vec![Arc::new(l1), Arc::new(l2)]);
let src = multi.find("a.ftl").unwrap().expect("第一个 loader 命中");
assert_eq!(src.name(), "a.ftl");
assert_eq!(multi.read(&*src).unwrap(), "from-first");
assert_eq!(multi.last_modified(&*src).unwrap(), 0);
assert_eq!(finds1.load(Ordering::SeqCst), 1);
assert_eq!(
finds2.load(Ordering::SeqCst),
0,
"第一个命中后不应再查第二个"
);
}
#[test]
fn first_miss_then_second_hit() {
let finds1 = Arc::new(AtomicUsize::new(0));
let finds2 = Arc::new(AtomicUsize::new(0));
let l1 = CountingLoader::new(finds1.clone()); let l2 = CountingLoader::new(finds2.clone());
l2.inner.put("b.ftl", "from-second");
let multi = MultiLoader::new(vec![Arc::new(l1), Arc::new(l2)]);
let src = multi.find("b.ftl").unwrap().expect("第二个 loader 命中");
assert_eq!(multi.read(&*src).unwrap(), "from-second");
assert_eq!(finds1.load(Ordering::SeqCst), 1, "第一个 miss 后查第二个");
assert_eq!(finds2.load(Ordering::SeqCst), 1);
}
#[test]
fn all_miss_returns_none() {
let finds1 = Arc::new(AtomicUsize::new(0));
let finds2 = Arc::new(AtomicUsize::new(0));
let l1 = CountingLoader::new(finds1.clone());
let l2 = CountingLoader::new(finds2.clone());
let multi = MultiLoader::new(vec![Arc::new(l1), Arc::new(l2)]);
assert!(multi.find("nope.ftl").unwrap().is_none());
assert_eq!(finds1.load(Ordering::SeqCst), 1);
assert_eq!(
finds2.load(Ordering::SeqCst),
1,
"全部 miss 时两个 loader 都会被查询"
);
}
#[test]
fn reset_state_propagates() {
let resets = Arc::new(AtomicUsize::new(0));
let l1 = ResetCountingLoader::new(resets.clone());
let l2 = ResetCountingLoader::new(resets.clone());
let multi = MultiLoader::new(vec![Arc::new(l1), Arc::new(l2)]);
multi.reset_state();
assert_eq!(resets.load(Ordering::SeqCst), 2, "两个内部加载器都被重置");
let plain = CountingLoader::new(Arc::new(AtomicUsize::new(0)));
assert!(plain.as_stateful().is_none());
}
struct ResetCountingLoader {
resets: Arc<AtomicUsize>,
inner: Arc<StringLoader>,
}
impl ResetCountingLoader {
fn new(resets: Arc<AtomicUsize>) -> Self {
ResetCountingLoader {
resets,
inner: Arc::new(StringLoader::default()),
}
}
}
impl TemplateLoader for ResetCountingLoader {
fn find(&self, name: &str) -> Result<Option<Box<dyn TemplateSource>>> {
self.inner.find(name)
}
fn read(&self, src: &dyn TemplateSource) -> Result<String> {
self.inner.read(src)
}
fn as_stateful(&self) -> Option<&dyn StatefulTemplateLoader> {
Some(self)
}
}
impl StatefulTemplateLoader for ResetCountingLoader {
fn reset_state(&self) {
self.resets.fetch_add(1, Ordering::SeqCst);
}
}
}