use std::io;
use std::path::PathBuf;
use axum::BoxError;
use bytes::Bytes;
use eyre::eyre;
use futures::{Stream, TryStreamExt};
use tokio::io::{AsyncReadExt, BufWriter};
use tokio_util::io::StreamReader;
use crate::error::Error;
use crate::foundation::storage_path;
pub struct File {
name: String,
content: Bytes,
}
impl File {
pub fn new(name: String, content: Bytes) -> Self {
Self { name, content }
}
pub fn path(&self) -> PathBuf {
storage_path(&self.name)
}
}
pub fn is_valid(path: &str) -> bool {
let path = std::path::Path::new(path);
let mut components = path.components().peekable();
if let Some(first) = components.peek() {
if !matches!(first, std::path::Component::Normal(_)) {
return false;
}
}
components.count() == 1
}
pub async fn store<S, E>(path: &str, stream: S) -> eyre::Result<PathBuf>
where
S: Stream<Item = Result<Bytes, E>>,
E: Into<BoxError>,
{
if !is_valid(path) {
return Err(eyre!(Error::BadRequest("Invalid file path".to_owned())));
}
let body_with_io_error = stream.map_err(|err| io::Error::new(io::ErrorKind::Other, err));
let body_reader = StreamReader::new(body_with_io_error);
futures::pin_mut!(body_reader);
let path = storage_path(path);
let mut file = BufWriter::new(tokio::fs::File::create(path.clone()).await?);
tokio::io::copy(&mut body_reader, &mut file).await?;
Ok(path.to_owned())
}
pub async fn get(path: &PathBuf) -> Result<Bytes, Error> {
let mut file = tokio::fs::File::open(path).await?;
let mut contents = vec![];
file.read_to_end(&mut contents).await?;
Ok(Bytes::from(contents))
}