use super::error::{Error, ErrorKind};
use crate::auth::UserDetail;
use async_trait::async_trait;
use chrono::{
Datelike,
prelude::{DateTime, Utc},
};
use md5::{Digest, Md5};
use std::{
fmt::{self, Debug, Formatter, Write},
io,
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)
}
fn readlink(&self) -> Option<&Path> {
None
}
}
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(|modified| {
let modified = DateTime::<Utc>::from(modified);
let now = Utc::now();
if modified.year() == now.year() {
modified.format("%b %d %H:%M").to_string()
} else {
modified.format("%b %d %Y").to_string()
}
})
.unwrap_or_else(|_| "--- -- --:--".to_string());
let basename = self.path.as_ref().components().next_back();
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());
let link_target = if self.metadata.is_symlink() {
match self.metadata.readlink() {
Some(t) => format!(" -> {}", t.display()),
None => {
"".to_string()
}
}
} else {
"".to_string()
};
write!(
f,
"{filetype}{permissions} {links:>12} {owner:>12} {group:>12} {size:#14} {modified:>12} {path}{link_target}",
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 enter(&mut self, _user_detail: &User) -> io::Result<()> {
Ok(())
}
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)]
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 buffer = list.iter().fold(String::new(), |mut buf, fi| {
let _ = write!(buf, "{}\r\n", fi);
buf
});
let file_infos: Vec<u8> = buffer.into_bytes();
Ok(std::io::Cursor::new(file_infos))
}
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)]
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 buffer = list.iter().fold(String::new(), |mut buf, fi| {
let _ = write!(
buf,
"{}\r\n",
fi.path.file_name().unwrap_or_else(|| std::ffi::OsStr::new("")).to_str().unwrap_or("")
);
buf
});
let file_infos: Vec<u8> = buffer.into_bytes();
Ok(std::io::Cursor::new(file_infos))
}
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.map_err(Error::from)?)
}
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 {
let kind = err.kind();
let raw_os_error = err.raw_os_error();
match (kind, raw_os_error) {
(std::io::ErrorKind::NotFound, _) => Error::new(ErrorKind::PermanentFileNotAvailable, err),
(std::io::ErrorKind::AlreadyExists, _) => Error::new(ErrorKind::PermanentFileNotAvailable, err),
(std::io::ErrorKind::PermissionDenied, _) => Error::new(ErrorKind::PermissionDenied, err),
#[cfg(unix)]
(_, Some(libc::ENOTEMPTY)) => Error::new(ErrorKind::PermanentDirectoryNotEmpty, err),
#[cfg(unix)]
(_, Some(libc::ENOTDIR)) => Error::new(ErrorKind::PermanentDirectoryNotAvailable, err),
#[cfg(unix)]
(_, Some(libc::EISDIR) | Some(libc::EFBIG) | Some(libc::ESPIPE) | Some(libc::ENAMETOOLONG) | Some(libc::ELOOP)) => {
Error::new(ErrorKind::PermanentFileNotAvailable, err)
}
#[cfg(unix)]
(_, Some(libc::ENOSPC)) => Error::new(ErrorKind::InsufficientStorageSpaceError, err),
#[cfg(unix)]
(_, Some(libc::EROFS)) => Error::new(ErrorKind::PermissionDenied, err),
(std::io::ErrorKind::ConnectionReset, _) => Error::new(ErrorKind::ConnectionClosed, err),
(std::io::ErrorKind::BrokenPipe, _) => Error::new(ErrorKind::ConnectionClosed, err),
(std::io::ErrorKind::ConnectionAborted, _) => Error::new(ErrorKind::ConnectionClosed, err),
_ => Error::new(ErrorKind::LocalError, err),
}
}
}