use anyhow::{Context, Result};
use chrono::{DateTime, Local};
use futures::Stream;
use hyper::body::Bytes;
use mime_guess::{from_path, Mime};
use std::fs::Metadata;
use std::mem::MaybeUninit;
use std::path::PathBuf;
use std::pin::Pin;
use std::task::{self, Poll};
use tokio::io::{AsyncRead, ReadBuf};
pub const FILE_BUFFER_SIZE: usize = 8 * 1024;
pub type FileBuffer = Box<[MaybeUninit<u8>; FILE_BUFFER_SIZE]>;
#[derive(Debug)]
pub struct File {
pub path: PathBuf,
pub file: tokio::fs::File,
pub metadata: Metadata,
}
impl File {
pub fn new(path: PathBuf, file: tokio::fs::File, metadata: Metadata) -> Self {
File {
path,
file,
metadata,
}
}
pub fn mime(&self) -> Mime {
from_path(self.path.clone()).first_or_octet_stream()
}
pub fn size(&self) -> u64 {
self.metadata.len()
}
pub fn last_modified(&self) -> Result<DateTime<Local>> {
let modified = self
.metadata
.modified()
.context("Failed to read last modified time for file")?;
let modified: DateTime<Local> = modified.into();
Ok(modified)
}
#[allow(dead_code)]
pub fn bytes(self) -> Vec<u8> {
let byte_stream = ByteStream {
file: self.file,
buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]),
};
byte_stream
.buffer
.iter()
.map(|muint| unsafe { muint.assume_init() })
.collect::<Vec<u8>>()
}
}
pub struct ByteStream {
file: tokio::fs::File,
buffer: FileBuffer,
}
impl From<File> for ByteStream {
fn from(file: File) -> Self {
ByteStream {
file: file.file,
buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]),
}
}
}
impl Stream for ByteStream {
type Item = Result<Bytes>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
let ByteStream {
ref mut file,
ref mut buffer,
} = *self;
let mut read_buffer = ReadBuf::uninit(&mut buffer[..]);
match Pin::new(file).poll_read(cx, &mut read_buffer) {
Poll::Ready(Ok(())) => {
let filled = read_buffer.filled();
if filled.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(Bytes::copy_from_slice(filled))))
}
}
Poll::Ready(Err(error)) => Poll::Ready(Some(Err(error.into()))),
Poll::Pending => Poll::Pending,
}
}
}