use chrono::prelude::{DateTime, Utc};
use failure::{Backtrace, Context, Fail};
use futures::{Future, Stream};
use std::path::Path;
use std::time::SystemTime;
use std::{
fmt::{self, Display},
result,
};
pub const FEATURE_RESTART: u32 = 0b0000_0001;
#[derive(Debug)]
pub struct Error {
inner: Context<ErrorKind>,
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl Error {
pub fn kind(&self) -> ErrorKind {
*self.inner.get_context()
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error { inner: Context::new(kind) }
}
}
impl Fail for Error {
fn cause(&self) -> Option<&dyn Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum ErrorKind {
#[fail(display = "450 Transient file not available")]
TransientFileNotAvailable,
#[fail(display = "550 Permanent file not available")]
PermanentFileNotAvailable,
#[fail(display = "550 Permission denied")]
PermissionDenied,
#[fail(display = "451 Local error")]
LocalError,
#[fail(display = "551 Page type unknown")]
PageTypeUnknown,
#[fail(display = "452 Insufficient storage space error")]
InsufficientStorageSpaceError,
#[fail(display = "552 Exceeded storage allocation error")]
ExceededStorageAllocationError,
#[fail(display = "553 File name not allowed error")]
FileNameNotAllowedError,
}
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;
}
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 fmt::Formatter) -> fmt::Result {
let modified: DateTime<Utc> = DateTime::from(self.metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH));
#[allow(clippy::write_literal)]
write!(
f,
"{filetype}{permissions} {owner:>12} {group:>12} {size:#14} {modified} {path}",
filetype = if self.metadata.is_dir() {
"d"
} else if self.metadata.is_symlink() {
"l"
} else {
"-"
},
permissions = "rwxr-xr-x",
owner = self.metadata.uid(),
group = self.metadata.gid(),
size = self.metadata.len(),
modified = modified.format("%b %d %H:%M"),
path = self.path.as_ref().components().last().unwrap().as_os_str().to_string_lossy(),
)
}
}
pub trait StorageBackend<U: Send> {
type File;
type Metadata: Metadata;
fn supported_features(&self) -> u32 {
0
}
fn metadata<P: AsRef<Path>>(&self, user: &Option<U>, path: P) -> Box<dyn Future<Item = Self::Metadata, Error = Error> + Send>;
fn list<P: AsRef<Path>>(&self, user: &Option<U>, path: P) -> Box<dyn Stream<Item = Fileinfo<std::path::PathBuf, Self::Metadata>, Error = Error> + Send>
where
<Self as StorageBackend<U>>::Metadata: Metadata;
fn list_fmt<P: AsRef<Path>>(&self, user: &Option<U>, path: P) -> Box<dyn Future<Item = std::io::Cursor<Vec<u8>>, Error = std::io::Error> + Send>
where
Self::Metadata: Metadata + 'static,
{
let stream: Box<dyn Stream<Item = Fileinfo<std::path::PathBuf, Self::Metadata>, Error = Error> + Send> = self.list(user, path);
let fut = stream
.map(|file| format!("{}\r\n", file).into_bytes())
.concat2()
.map(std::io::Cursor::new)
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other));
Box::new(fut)
}
fn nlst<P: AsRef<Path>>(&self, user: &Option<U>, path: P) -> Box<dyn Future<Item = std::io::Cursor<Vec<u8>>, Error = std::io::Error> + Send>
where
Self::Metadata: Metadata + 'static,
{
let stream: Box<dyn Stream<Item = Fileinfo<std::path::PathBuf, Self::Metadata>, Error = Error> + Send> = self.list(user, path);
let fut = stream
.map(|file| {
format!(
"{}\r\n",
file.path.file_name().unwrap_or_else(|| std::ffi::OsStr::new("")).to_str().unwrap_or("")
)
.into_bytes()
})
.concat2()
.map(std::io::Cursor::new)
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other));
Box::new(fut)
}
fn get<P: AsRef<Path>>(&self, user: &Option<U>, path: P, start_pos: u64) -> Box<dyn Future<Item = Self::File, Error = Error> + Send>;
fn put<P: AsRef<Path>, R: tokio::prelude::AsyncRead + Send + 'static>(
&self,
user: &Option<U>,
bytes: R,
path: P,
start_pos: u64,
) -> Box<dyn Future<Item = u64, Error = Error> + Send>;
fn del<P: AsRef<Path>>(&self, user: &Option<U>, path: P) -> Box<dyn Future<Item = (), Error = Error> + Send>;
fn mkd<P: AsRef<Path>>(&self, user: &Option<U>, path: P) -> Box<dyn Future<Item = (), Error = Error> + Send>;
fn rename<P: AsRef<Path>>(&self, user: &Option<U>, from: P, to: P) -> Box<dyn Future<Item = (), Error = Error> + Send>;
fn rmd<P: AsRef<Path>>(&self, user: &Option<U>, path: P) -> Box<dyn Future<Item = (), Error = Error> + Send>;
}
pub mod filesystem;
#[cfg(feature = "cloud_storage")]
pub mod cloud_storage;