use super::error::Error;
use crate::auth::UserDetail;
use crate::storage::ErrorKind;
use async_trait::async_trait;
use chrono::prelude::{DateTime, Utc};
use md5::{Digest, Md5};
use std::{
fmt::{self, Debug, Formatter, Write},
path::Path,
result,
time::SystemTime,
};
use tokio::io::AsyncReadExt;
pub const FEATURE_RESTART: u32 = 0b0000_0001;
pub const FEATURE_SITEMD5: u32 = 0b0000_0010;
pub type Result<T> = result::Result<T, Error>;
pub trait Metadata {
fn len(&self) -> u64;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn is_dir(&self) -> bool;
fn is_file(&self) -> bool;
fn is_symlink(&self) -> bool;
fn modified(&self) -> Result<SystemTime>;
fn gid(&self) -> u32;
fn uid(&self) -> u32;
fn links(&self) -> u64 {
1
}
fn permissions(&self) -> Permissions {
Permissions(0o7755)
}
}
pub struct Permissions(pub u32);
const PERM_READ: u32 = 0b100100100;
const PERM_WRITE: u32 = 0b010010010;
const PERM_EXEC: u32 = 0b001001001;
const PERM_USER: u32 = 0b111000000;
const PERM_GROUP: u32 = 0b000111000;
const PERM_OTHERS: u32 = 0b000000111;
impl std::fmt::Display for Permissions {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_char(if self.0 & PERM_USER & PERM_READ > 0 { 'r' } else { '-' })?;
f.write_char(if self.0 & PERM_USER & PERM_WRITE > 0 { 'w' } else { '-' })?;
f.write_char(if self.0 & PERM_USER & PERM_EXEC > 0 { 'x' } else { '-' })?;
f.write_char(if self.0 & PERM_GROUP & PERM_READ > 0 { 'r' } else { '-' })?;
f.write_char(if self.0 & PERM_GROUP & PERM_WRITE > 0 { 'w' } else { '-' })?;
f.write_char(if self.0 & PERM_GROUP & PERM_EXEC > 0 { 'x' } else { '-' })?;
f.write_char(if self.0 & PERM_OTHERS & PERM_READ > 0 { 'r' } else { '-' })?;
f.write_char(if self.0 & PERM_OTHERS & PERM_WRITE > 0 { 'w' } else { '-' })?;
f.write_char(if self.0 & PERM_OTHERS & PERM_EXEC > 0 { 'x' } else { '-' })?;
Ok(())
}
}
#[derive(Clone)]
pub struct Fileinfo<P, M>
where
P: AsRef<Path>,
M: Metadata,
{
pub path: P,
pub metadata: M,
}
impl<P, M> std::fmt::Display for Fileinfo<P, M>
where
P: AsRef<Path>,
M: Metadata,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let modified: String = self
.metadata
.modified()
.map(|x| DateTime::<Utc>::from(x).format("%b %d %H:%M").to_string())
.unwrap_or_else(|_| "--- -- --:--".to_string());
let basename = self.path.as_ref().components().last();
let path = match basename {
Some(v) => v.as_os_str().to_string_lossy(),
None => {
return Err(std::fmt::Error);
}
};
let perms = format!("{}", self.metadata.permissions());
write!(
f,
"{filetype}{permissions} {links:>12} {owner:>12} {group:>12} {size:#14} {modified:>12} {path}",
filetype = if self.metadata.is_dir() {
"d"
} else if self.metadata.is_symlink() {
"l"
} else {
"-"
},
permissions = perms,
links = self.metadata.links(),
owner = self.metadata.uid(),
group = self.metadata.gid(),
size = self.metadata.len(),
modified = modified,
path = path,
)
}
}
#[async_trait]
pub trait StorageBackend<User: UserDetail>: Send + Sync + Debug {
type Metadata: Metadata + Sync + Send;
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
fn supported_features(&self) -> u32 {
0
}
async fn metadata<P: AsRef<Path> + Send + Debug>(&self, user: &User, path: P) -> Result<Self::Metadata>;
async fn md5<P: AsRef<Path> + Send + Debug>(&self, user: &User, path: P) -> Result<String>
where
P: AsRef<Path> + Send + Debug,
{
let mut md5sum = Md5::new();
let mut reader = self.get(user, path, 0).await?;
let mut buffer = vec![0_u8; 1024 * 1024 * 10];
while let Ok(n) = reader.read(&mut buffer[..]).await {
if n == 0 {
break;
}
md5sum.update(&buffer[0..n]);
}
Ok(format!("{:x}", md5sum.finalize()))
}
async fn list<P: AsRef<Path> + Send + Debug>(&self, user: &User, path: P) -> Result<Vec<Fileinfo<std::path::PathBuf, Self::Metadata>>>
where
<Self as StorageBackend<User>>::Metadata: Metadata;
#[allow(clippy::type_complexity)]
#[tracing_attributes::instrument]
async fn list_fmt<P>(&self, user: &User, path: P) -> std::result::Result<std::io::Cursor<Vec<u8>>, Error>
where
P: AsRef<Path> + Send + Debug,
Self::Metadata: Metadata + 'static,
{
let list = self.list(user, path).await?;
let file_infos: Vec<u8> = list.iter().map(|fi| format!("{}\r\n", fi)).collect::<String>().into_bytes();
Ok(std::io::Cursor::new(file_infos))
}
#[tracing_attributes::instrument]
async fn list_vec<P>(&self, user: &User, path: P) -> std::result::Result<Vec<String>, Error>
where
P: AsRef<Path> + Send + Debug,
Self::Metadata: Metadata + 'static,
{
let inlist = self.list(user, path).await?;
let out = inlist.iter().map(|fi| fi.to_string()).collect::<Vec<String>>();
Ok(out)
}
#[allow(clippy::type_complexity)]
#[tracing_attributes::instrument]
async fn nlst<P>(&self, user: &User, path: P) -> std::result::Result<std::io::Cursor<Vec<u8>>, std::io::Error>
where
P: AsRef<Path> + Send + Debug,
Self::Metadata: Metadata + 'static,
{
let list = self.list(user, path).await.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))?;
let bytes = list
.iter()
.map(|file| {
let info = file.path.file_name().unwrap_or_else(|| std::ffi::OsStr::new("")).to_str().unwrap_or("");
format!("{}\r\n", info)
})
.collect::<String>()
.into_bytes();
Ok(std::io::Cursor::new(bytes))
}
async fn get_into<'a, P, W: ?Sized>(&self, user: &User, path: P, start_pos: u64, output: &'a mut W) -> Result<u64>
where
W: tokio::io::AsyncWrite + Unpin + Sync + Send,
P: AsRef<Path> + Send + Debug,
{
let mut reader = self.get(user, path, start_pos).await?;
Ok(tokio::io::copy(&mut reader, output).await?)
}
async fn get<P: AsRef<Path> + Send + Debug>(&self, user: &User, path: P, start_pos: u64) -> Result<Box<dyn tokio::io::AsyncRead + Send + Sync + Unpin>>;
async fn put<P: AsRef<Path> + Send + Debug, R: tokio::io::AsyncRead + Send + Sync + Unpin + 'static>(
&self,
user: &User,
input: R,
path: P,
start_pos: u64,
) -> Result<u64>;
async fn del<P: AsRef<Path> + Send + Debug>(&self, user: &User, path: P) -> Result<()>;
async fn mkd<P: AsRef<Path> + Send + Debug>(&self, user: &User, path: P) -> Result<()>;
async fn rename<P: AsRef<Path> + Send + Debug>(&self, user: &User, from: P, to: P) -> Result<()>;
async fn rmd<P: AsRef<Path> + Send + Debug>(&self, user: &User, path: P) -> Result<()>;
async fn cwd<P: AsRef<Path> + Send + Debug>(&self, user: &User, path: P) -> Result<()>;
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
match err.kind() {
std::io::ErrorKind::NotFound => Error::from(ErrorKind::PermanentFileNotAvailable),
std::io::ErrorKind::PermissionDenied => Error::from(ErrorKind::PermissionDenied),
_ => Error::new(ErrorKind::LocalError, err),
}
}
}