use std::fs::File;
use std::future::{ready, Future};
use std::io::{BufReader, Seek, SeekFrom};
use std::pin::Pin;
use headers::{ContentType, HeaderMapExt, Range};
use http::Method;
use net_traits::request::Request;
use net_traits::response::{Response, ResponseBody};
use net_traits::{NetworkError, ResourceFetchTiming};
use tokio::sync::mpsc::unbounded_channel;
use crate::fetch::methods::{DoneChannel, FetchContext};
use crate::filemanager_thread::FILE_CHUNK_SIZE;
use crate::local_directory_listing;
use crate::protocols::{
get_range_request_bounds, partial_content, range_not_satisfiable_error, ProtocolHandler,
};
#[derive(Default)]
pub struct FileProtocolHander {}
impl ProtocolHandler for FileProtocolHander {
fn load(
&self,
request: &mut Request,
done_chan: &mut DoneChannel,
context: &FetchContext,
) -> Pin<Box<dyn Future<Output = Response> + Send>> {
let url = request.current_url();
if request.method != Method::GET {
return Box::pin(ready(Response::network_error(NetworkError::Internal(
"Unexpected method for file".into(),
))));
}
let response = if let Ok(file_path) = url.to_file_path() {
if file_path.is_dir() {
return Box::pin(ready(local_directory_listing::fetch(
request, url, file_path,
)));
}
if let Ok(file) = File::open(file_path.clone()) {
let file_size = match file.metadata() {
Ok(metadata) => Some(metadata.len()),
Err(_) => None,
};
let mut response =
Response::new(url, ResourceFetchTiming::new(request.timing_type()));
let range_header = request.headers.typed_get::<Range>();
let is_range_request = range_header.is_some();
let Ok(range) = get_range_request_bounds(range_header).get_final(file_size) else {
range_not_satisfiable_error(&mut response);
return Box::pin(ready(response));
};
let mut reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
if reader.seek(SeekFrom::Start(range.start as u64)).is_err() {
return Box::pin(ready(Response::network_error(NetworkError::Internal(
"Unexpected method for file".into(),
))));
}
if is_range_request {
partial_content(&mut response);
}
let mime = mime_guess::from_path(file_path).first_or_octet_stream();
response.headers.typed_insert(ContentType::from(mime));
let (mut done_sender, done_receiver) = unbounded_channel();
*done_chan = Some((done_sender.clone(), done_receiver));
*response.body.lock().unwrap() = ResponseBody::Receiving(vec![]);
context.filemanager.lock().unwrap().fetch_file_in_chunks(
&mut done_sender,
reader,
response.body.clone(),
context.cancellation_listener.clone(),
range,
);
response
} else {
Response::network_error(NetworkError::Internal("Opening file failed".into()))
}
} else {
Response::network_error(NetworkError::Internal(
"Constructing file path failed".into(),
))
};
Box::pin(ready(response))
}
}