use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc};
use actix_service::ServiceFactory;
use actix_web::{
HttpRequest,
dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse},
error::Error,
guard::Guard,
http::header::ContentEncoding,
};
use futures_core::future::LocalBoxFuture;
use crate::{
config::{PreCompType, PreCompressed},
files::directory::{Directory, DirectoryRenderer, directory_listing},
files::service::{FilesService, FilesServiceInner},
};
use crate::files::service::HttpNewService;
#[derive(Clone, Debug)]
pub struct CompressedFileTypes {
pre: Vec<(ContentEncoding, String)>,
}
impl From<&Vec<PreCompressed>> for CompressedFileTypes {
fn from(value: &Vec<PreCompressed>) -> Self {
let source = if value.is_empty() {
PreCompressed::defaults()
} else {
value.clone()
};
let mut pre = Vec::new();
for p in source {
let enc = match p.compression_type {
PreCompType::Gzip => ContentEncoding::Gzip,
PreCompType::Brotli => ContentEncoding::Brotli,
PreCompType::Zstd => ContentEncoding::Zstd,
PreCompType::None => ContentEncoding::Identity,
};
pre.push((enc, p.extension.to_owned()));
}
Self { pre }
}
}
impl CompressedFileTypes {
pub fn iter(&self) -> std::slice::Iter<'_, (ContentEncoding, String)> {
self.pre.iter()
}
}
pub struct CompressedFiles {
mount_path: String,
directory: PathBuf,
index: Option<String>,
show_index: bool,
redirect_to_slash: bool,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
renderer: Rc<DirectoryRenderer>,
use_guards: Option<Rc<dyn Guard>>,
guards: Vec<Rc<dyn Guard>>,
hidden_files: bool,
pre_compressed: Option<CompressedFileTypes>,
}
impl fmt::Debug for CompressedFiles {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Files")
}
}
impl Clone for CompressedFiles {
fn clone(&self) -> Self {
Self {
directory: self.directory.clone(),
index: self.index.clone(),
show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: self.default.clone(),
renderer: self.renderer.clone(),
mount_path: self.mount_path.clone(),
use_guards: self.use_guards.clone(),
guards: self.guards.clone(),
hidden_files: self.hidden_files,
pre_compressed: self.pre_compressed.clone(),
}
}
}
impl CompressedFiles {
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> CompressedFiles {
let orig_dir = serve_from.into();
let dir = match orig_dir.canonicalize() {
Ok(canon_dir) => canon_dir,
Err(_) => {
log::error!("Specified path is not a directory: {:?}", orig_dir);
PathBuf::new()
}
};
CompressedFiles {
mount_path: mount_path.trim_end_matches('/').to_owned(),
directory: dir,
index: None,
show_index: false,
redirect_to_slash: false,
default: Rc::new(RefCell::new(None)),
renderer: Rc::new(directory_listing),
use_guards: None,
guards: Vec::new(),
hidden_files: false,
pre_compressed: None,
}
}
pub fn show_files_listing(mut self) -> Self {
self.show_index = true;
self
}
#[allow(unused)]
pub fn redirect_to_slash_directory(mut self) -> Self {
self.redirect_to_slash = true;
self
}
#[allow(unused)]
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
where
for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error> + 'static,
{
self.renderer = Rc::new(f);
self
}
pub fn index_file<T: Into<String>>(mut self, index: T) -> Self {
self.index = Some(index.into());
self
}
#[allow(unused)]
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.guards.push(Rc::new(guard));
self
}
#[allow(unused)]
pub fn method_guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.use_guards = Some(Rc::new(guard));
self
}
#[allow(unused)]
pub fn use_hidden_files(mut self) -> Self {
self.hidden_files = true;
self
}
pub fn pre_compressed(mut self, pre: CompressedFileTypes) -> Self {
self.pre_compressed = Some(pre);
self
}
}
impl HttpServiceFactory for CompressedFiles {
fn register(mut self, config: &mut AppService) {
let guards = if self.guards.is_empty() {
None
} else {
let guards = std::mem::take(&mut self.guards);
Some(
guards
.into_iter()
.map(|guard| -> Box<dyn Guard> { Box::new(guard) })
.collect::<Vec<_>>(),
)
};
if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service());
}
let rdef = if config.is_root() {
ResourceDef::root_prefix(&self.mount_path)
} else {
ResourceDef::prefix(&self.mount_path)
};
config.register_service(rdef, guards, self, None)
}
}
impl ServiceFactory<ServiceRequest> for CompressedFiles {
type Response = ServiceResponse;
type Error = Error;
type Config = ();
type Service = FilesService;
type InitError = ();
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
let mut inner = FilesServiceInner {
directory: self.directory.clone(),
index: self.index.clone(),
show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: None,
renderer: self.renderer.clone(),
guards: self.use_guards.clone(),
hidden_files: self.hidden_files,
pre_compressed: self.pre_compressed.clone(),
};
if let Some(ref default) = *self.default.borrow() {
let fut = default.new_service(());
Box::pin(async {
match fut.await {
Ok(default) => {
inner.default = Some(default);
Ok(FilesService(Rc::new(inner)))
}
Err(_) => Err(()),
}
})
} else {
Box::pin(async move { Ok(FilesService(Rc::new(inner))) })
}
}
}