bazzar/actors/fs_manager/src/lib.rs

237 lines
6.4 KiB
Rust
Raw Normal View History

2022-05-06 11:47:18 +02:00
#![feature(io_error_more)]
use std::ffi::OsStr;
use std::io::Write;
use config::SharedAppConfig;
2022-05-09 16:17:27 +02:00
use model::{FileName, LocalPath, UniqueName};
2022-05-06 11:47:18 +02:00
#[macro_export]
macro_rules! fs_async_handler {
($msg: ty, $async: ident, $res: ty) => {
impl actix::Handler<$msg> for FsManager {
type Result = actix::ResponseActFuture<Self, Result<$res>>;
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
use actix::WrapFuture;
let config = self.config.clone();
Box::pin(async { $async(msg, config).await }.into_actor(self))
}
}
};
}
2022-05-08 09:47:05 +02:00
#[macro_export]
macro_rules! query_fs {
($fs: expr, $msg: expr, default $fail: expr) => {
match $fs.send($msg).await {
Ok(Ok(r)) => r,
Ok(Err(e)) => {
log::error!("{e}");
$fail
}
Err(e) => {
log::error!("{e:?}");
$fail
}
}
};
($fs: expr, $msg: expr, panic) => {
match $fs.send($msg).await {
Ok(Ok(r)) => r,
Ok(Err(e)) => {
log::error!("{e}");
panic!("{:?}", e);
}
Err(e) => {
log::error!("{e:?}");
panic!("{:?}", e);
}
}
};
($fs: expr, $msg: expr, $fail: expr) => {
2022-05-10 16:20:37 +02:00
$crate::query_fs!($fs, $msg, $fail, $fail)
2022-05-08 09:47:05 +02:00
};
($fs: expr, $msg: expr, $db_fail: expr, $act_fail: expr) => {
match $fs.send($msg).await {
Ok(Ok(r)) => r,
Ok(Err(e)) => {
log::error!("{e}");
return Err($db_fail);
}
Err(e) => {
log::error!("{e:?}");
return Err($act_fail);
}
}
};
}
2022-05-22 14:19:11 +02:00
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
#[serde(rename_all = "kebab-case", tag = "fs")]
2022-05-06 11:47:18 +02:00
pub enum Error {
#[error("Can't access file system. Please check privileges")]
StorageUnavailable,
2022-05-22 14:19:11 +02:00
#[error("Can't write to file. Please check privileges.")]
CantWrite,
2022-05-06 11:47:18 +02:00
#[error("Can't write to file. There's no more space on disk")]
NoSpace,
2022-05-22 14:19:11 +02:00
#[error("Can't remove file. Please check privileges.")]
CantRemove,
2022-05-06 11:47:18 +02:00
#[error("Can't write to file. Invalid path, no filename")]
InvalidPath,
}
pub type Result<T> = std::result::Result<T, Error>;
2022-05-06 16:02:38 +02:00
#[derive(Clone)]
2022-05-06 11:47:18 +02:00
pub struct FsManager {
config: SharedAppConfig,
}
impl actix::Actor for FsManager {
type Context = actix::Context<FsManager>;
}
impl FsManager {
pub async fn build(config: SharedAppConfig) -> Result<Self> {
Self::validate_fs(config.clone()).await?;
Ok(Self { config })
}
async fn validate_fs(config: SharedAppConfig) -> Result<()> {
let l = config.lock();
let output_path = l.files().local_path();
2022-05-06 16:02:38 +02:00
tokio::fs::create_dir_all(&output_path).await.ok();
2022-05-06 11:47:18 +02:00
let test_file = std::path::PathBuf::new()
.join(output_path)
.join(format!("{}", uuid::Uuid::new_v4()));
tokio::fs::remove_file(test_file.clone()).await.ok();
tokio::fs::write(test_file.clone(), b"1")
.await
.map_err(|_| Error::StorageUnavailable)?;
tokio::fs::remove_file(test_file.clone())
.await
.map_err(|_| Error::StorageUnavailable)
}
}
#[derive(actix::Message)]
#[rtype(result = "Result<()>")]
pub struct RemoveFile {
pub file_name: String,
}
fs_async_handler!(RemoveFile, remove_file, ());
pub(crate) async fn remove_file(msg: RemoveFile, config: SharedAppConfig) -> Result<()> {
let output_path = {
let l = config.lock();
l.files().local_path()
};
match tokio::fs::remove_file(
std::path::PathBuf::new()
.join(output_path)
.join(msg.file_name),
)
.await
{
Ok(_) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
2022-05-22 14:19:11 +02:00
Err(e) => {
log::error!("{:?}", e);
Err(Error::CantRemove)
}
2022-05-06 11:47:18 +02:00
}
}
2022-05-06 16:02:38 +02:00
pub struct WriteResult {
2022-05-08 09:47:05 +02:00
/// Unique file name created with UUID and original file extension
2022-05-09 16:17:27 +02:00
pub unique_name: UniqueName,
2022-05-08 09:47:05 +02:00
/// Path to file in local storage
2022-05-06 16:02:38 +02:00
pub local_path: LocalPath,
2022-05-09 16:17:27 +02:00
pub file_name: FileName,
2022-05-06 16:02:38 +02:00
}
2022-05-06 11:47:18 +02:00
#[derive(actix::Message)]
2022-05-06 16:02:38 +02:00
#[rtype(result = "Result<WriteResult>")]
2022-05-06 11:47:18 +02:00
pub struct WriteFile {
pub file_name: String,
pub stream: tokio::sync::mpsc::UnboundedReceiver<actix_web::web::Bytes>,
2022-05-06 11:47:18 +02:00
}
2022-05-06 16:02:38 +02:00
fs_async_handler!(WriteFile, write_file, WriteResult);
2022-05-06 11:47:18 +02:00
2022-05-06 16:02:38 +02:00
pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Result<WriteResult> {
2022-05-06 11:47:18 +02:00
let WriteFile {
file_name,
mut stream,
} = msg;
2022-05-08 09:47:05 +02:00
log::debug!("Writing file {:?}", file_name);
2022-05-06 11:47:18 +02:00
let p = std::path::Path::new(&file_name);
let ext = p
.file_name()
.and_then(OsStr::to_str)
.map(std::path::Path::new)
.and_then(std::path::Path::extension)
.and_then(OsStr::to_str)
.map(String::from);
2022-05-08 09:47:05 +02:00
let unique_name = format!(
2022-05-06 11:47:18 +02:00
"{}{}",
uuid::Uuid::new_v4(),
ext.map(|s| format!(".{s}")).unwrap_or_default()
);
let output_path = {
let l = config.lock();
l.files().local_path()
};
2022-05-06 16:02:38 +02:00
let path = std::path::PathBuf::new()
.join(&output_path)
2022-05-08 09:47:05 +02:00
.join(&unique_name);
log::debug!(
"File {:?} will be written as {:?} at {:?}",
file_name,
unique_name,
path
);
2022-05-06 16:02:38 +02:00
let mut file = match std::fs::File::create(&path) {
2022-05-06 11:47:18 +02:00
Ok(f) => f,
2022-05-22 14:19:11 +02:00
Err(e) => {
log::error!("{:?}", e);
return Err(Error::CantWrite);
}
2022-05-06 11:47:18 +02:00
};
2022-05-08 09:47:05 +02:00
let mut counter = 0;
2022-05-06 11:47:18 +02:00
while let Some(b) = stream.recv().await {
2022-05-08 09:47:05 +02:00
counter += 1;
if counter % 100_000 == 0 {
log::debug!("Wrote {} for {:?}", counter, file_name);
}
match file.write(&b) {
2022-05-06 11:47:18 +02:00
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::StorageFull => return Err(Error::NoSpace),
2022-05-22 14:19:11 +02:00
Err(e) => {
log::error!("{:?}", e);
return Err(Error::CantWrite);
}
2022-05-06 11:47:18 +02:00
}
}
2022-05-08 09:47:05 +02:00
log::debug!("File {:?} successfully written", unique_name);
2022-05-06 16:02:38 +02:00
Ok(WriteResult {
2022-05-09 16:17:27 +02:00
file_name: FileName::new(file_name),
unique_name: UniqueName::new(unique_name),
local_path: LocalPath::new(path.to_str().unwrap_or_default()),
2022-05-06 16:02:38 +02:00
})
2022-05-06 11:47:18 +02:00
}