use std::path::Path;
use std::sync::Arc;
use std::{error, fmt, io, result};
use bytes::Bytes;
use derive_more::Display;
use url::Url;
use crate::*;
pub mod local;
use local::LocalTransport;
#[cfg(feature = "s3")]
pub mod s3;
pub fn open_transport(s: &str) -> crate::Result<Arc<dyn Transport>> {
if let Ok(url) = Url::parse(s) {
match url.scheme() {
"file" => Ok(Arc::new(LocalTransport::new(
&url.to_file_path().expect("extract URL file path"),
))),
#[cfg(feature = "s3")]
"s3" => Ok(s3::S3Transport::new(&url)?),
d if d.len() == 1 => {
Ok(Arc::new(LocalTransport::new(Path::new(s))))
}
other => Err(crate::Error::UrlScheme {
scheme: other.to_owned(),
}),
}
} else {
Ok(Arc::new(LocalTransport::new(Path::new(s))))
}
}
pub fn open_local_transport(path: &Path) -> crate::Result<Arc<dyn Transport>> {
Ok(Arc::new(LocalTransport::new(path)))
}
pub trait Transport: Send + Sync + std::fmt::Debug {
fn list_dir(&self, relpath: &str) -> Result<ListDir>;
fn read_file(&self, path: &str) -> Result<Bytes>;
fn is_file(&self, path: &str) -> Result<bool> {
match self.metadata(path) {
Ok(metadata) => Ok(metadata.kind == Kind::File),
Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
Err(err) => Err(err),
}
}
fn create_dir(&self, relpath: &str) -> Result<()>;
fn write_file(&self, relpath: &str, content: &[u8]) -> Result<()>;
fn metadata(&self, relpath: &str) -> Result<Metadata>;
fn remove_file(&self, relpath: &str) -> Result<()>;
fn remove_dir_all(&self, relpath: &str) -> Result<()>;
fn sub_transport(&self, relpath: &str) -> Arc<dyn Transport>;
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct DirEntry {
pub name: String,
pub kind: Kind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Metadata {
pub len: u64,
pub kind: Kind,
}
#[derive(Debug, Default, Eq, PartialEq)]
pub struct ListDir {
pub files: Vec<String>,
pub dirs: Vec<String>,
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
source: Option<Box<dyn error::Error + Send + Sync>>,
path: Option<String>,
}
#[derive(Debug, Display, PartialEq, Eq, Clone, Copy)]
pub enum ErrorKind {
#[display(fmt = "Not found")]
NotFound,
#[display(fmt = "Already exists")]
AlreadyExists,
#[display(fmt = "Permission denied")]
PermissionDenied,
#[display(fmt = "Other transport error")]
Other,
}
impl Error {
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub(self) fn io_error(path: &Path, source: io::Error) -> Error {
let kind = match source.kind() {
io::ErrorKind::NotFound => ErrorKind::NotFound,
io::ErrorKind::AlreadyExists => ErrorKind::AlreadyExists,
io::ErrorKind::PermissionDenied => ErrorKind::PermissionDenied,
_ => ErrorKind::Other,
};
Error {
source: Some(Box::new(source)),
path: Some(path.to_string_lossy().to_string()),
kind,
}
}
pub fn is_not_found(&self) -> bool {
self.kind == ErrorKind::NotFound
}
pub fn path(&self) -> Option<&str> {
self.path.as_deref()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(ref path) = self.path {
write!(f, ": {}", path)?;
}
if let Some(source) = &self.source {
write!(f, ": {source}")?;
}
Ok(())
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.source.as_ref().map(|s| &**s as _)
}
}
type Result<T> = result::Result<T, Error>;