use std::fmt::Debug;
use std::future::{self, Future};
use std::io::SeekFrom;
use std::pin::Pin;
use std::time::{SystemTime, UNIX_EPOCH};
use dyn_clone::{DynClone, clone_trait_object};
use futures_util::{FutureExt, Stream, TryFutureExt};
use http::StatusCode;
use crate::davpath::DavPath;
macro_rules! notimplemented {
($method:expr_2021) => {
Err(FsError::NotImplemented)
};
}
macro_rules! notimplemented_fut {
($method:expr_2021) => {
Box::pin(future::ready(Err(FsError::NotImplemented)))
};
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FsError {
NotImplemented,
GeneralFailure,
Exists,
NotFound,
Forbidden,
InsufficientStorage,
LoopDetected,
PathTooLong,
TooLarge,
IsRemote,
}
pub type FsResult<T> = std::result::Result<T, FsError>;
#[cfg(any(feature = "memfs", feature = "localfs"))]
impl From<&std::io::Error> for FsError {
fn from(e: &std::io::Error) -> Self {
use std::io::ErrorKind;
if let Some(errno) = e.raw_os_error() {
match errno {
#[cfg(unix)]
libc::EMLINK | libc::ENOSPC | libc::EDQUOT => return FsError::InsufficientStorage,
#[cfg(windows)]
libc::EMLINK | libc::ENOSPC => return FsError::InsufficientStorage,
libc::EFBIG => return FsError::TooLarge,
libc::EACCES | libc::EPERM => return FsError::Forbidden,
libc::ENOTEMPTY | libc::EEXIST => return FsError::Exists,
libc::ELOOP => return FsError::LoopDetected,
libc::ENAMETOOLONG => return FsError::PathTooLong,
libc::ENOTDIR => return FsError::Forbidden,
libc::EISDIR => return FsError::Forbidden,
libc::EROFS => return FsError::Forbidden,
libc::ENOENT => return FsError::NotFound,
libc::ENOSYS => return FsError::NotImplemented,
libc::EXDEV => return FsError::IsRemote,
_ => {}
}
} else {
return FsError::NotImplemented;
}
match e.kind() {
ErrorKind::NotFound => FsError::NotFound,
ErrorKind::PermissionDenied => FsError::Forbidden,
_ => FsError::GeneralFailure,
}
}
}
#[cfg(any(feature = "memfs", feature = "localfs"))]
impl From<std::io::Error> for FsError {
fn from(e: std::io::Error) -> Self {
(&e).into()
}
}
#[derive(Debug, Clone)]
pub struct DavProp {
pub name: String,
pub prefix: Option<String>,
pub namespace: Option<String>,
pub xml: Option<Vec<u8>>,
}
impl DavProp {
pub fn new(name: String, prefix: String, namespace: String, value: String) -> DavProp {
DavProp {
name: name.clone(),
prefix: Some(prefix.clone()),
namespace: Some(namespace.clone()),
xml: Some(
format!(
"<{prefix}:{name} xmlns:{prefix}=\"{namespace}\">{value}</{prefix}:{name}>"
)
.into_bytes(),
),
}
}
}
pub type FsFuture<'a, T> = Pin<Box<dyn Future<Output = FsResult<T>> + Send + 'a>>;
pub type FsStream<T> = Pin<Box<dyn Stream<Item = FsResult<T>> + Send>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReadDirMeta {
Data,
DataSymlink,
None,
}
pub trait DavFileSystem {
fn open<'a>(
&'a self,
path: &'a DavPath,
options: OpenOptions,
) -> FsFuture<'a, Box<dyn DavFile>>;
fn read_dir<'a>(
&'a self,
path: &'a DavPath,
meta: ReadDirMeta,
) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;
fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>>;
#[allow(unused_variables)]
fn symlink_metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
self.metadata(path)
}
#[allow(unused_variables)]
fn create_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
notimplemented_fut!("create_dir")
}
#[allow(unused_variables)]
fn remove_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
notimplemented_fut!("remove_dir")
}
#[allow(unused_variables)]
fn remove_file<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
notimplemented_fut!("remove_file")
}
#[allow(unused_variables)]
fn rename<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
notimplemented_fut!("rename")
}
#[allow(unused_variables)]
fn copy<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
notimplemented_fut!("copy")
}
#[doc(hidden)]
#[allow(unused_variables)]
fn set_accessed<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<'a, ()> {
notimplemented_fut!("set_accessed")
}
#[doc(hidden)]
#[allow(unused_variables)]
fn set_modified<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<'a, ()> {
notimplemented_fut!("set_modified")
}
#[allow(unused_variables)]
fn have_props<'a>(
&'a self,
path: &'a DavPath,
) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
Box::pin(future::ready(false))
}
#[allow(unused_variables)]
fn patch_props<'a>(
&'a self,
path: &'a DavPath,
patch: Vec<(bool, DavProp)>,
) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
notimplemented_fut!("patch_props")
}
#[allow(unused_variables)]
fn get_props<'a>(&'a self, path: &'a DavPath, do_content: bool) -> FsFuture<'a, Vec<DavProp>> {
notimplemented_fut!("get_props")
}
#[allow(unused_variables)]
fn get_prop<'a>(&'a self, path: &'a DavPath, prop: DavProp) -> FsFuture<'a, Vec<u8>> {
notimplemented_fut!("get_prop")
}
#[allow(unused_variables)]
fn get_quota(&'_ self) -> FsFuture<'_, (u64, Option<u64>)> {
notimplemented_fut!("get_quota")
}
}
pub trait GuardedFileSystem<C>: Send + Sync + DynClone
where
C: Clone + Send + Sync + 'static,
{
fn open<'a>(
&'a self,
path: &'a DavPath,
options: OpenOptions,
credentials: &'a C,
) -> FsFuture<'a, Box<dyn DavFile>>;
fn read_dir<'a>(
&'a self,
path: &'a DavPath,
meta: ReadDirMeta,
credentials: &'a C,
) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;
fn metadata<'a>(
&'a self,
path: &'a DavPath,
credentials: &'a C,
) -> FsFuture<'a, Box<dyn DavMetaData>>;
#[allow(unused_variables)]
fn symlink_metadata<'a>(
&'a self,
path: &'a DavPath,
credentials: &'a C,
) -> FsFuture<'a, Box<dyn DavMetaData>> {
self.metadata(path, credentials)
}
#[allow(unused_variables)]
fn create_dir<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
notimplemented_fut!("create_dir")
}
#[allow(unused_variables)]
fn remove_dir<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
notimplemented_fut!("remove_dir")
}
#[allow(unused_variables)]
fn remove_file<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
notimplemented_fut!("remove_file")
}
#[allow(unused_variables)]
fn rename<'a>(
&'a self,
from: &'a DavPath,
to: &'a DavPath,
credentials: &'a C,
) -> FsFuture<'a, ()> {
notimplemented_fut!("rename")
}
#[allow(unused_variables)]
fn copy<'a>(
&'a self,
from: &'a DavPath,
to: &'a DavPath,
credentials: &'a C,
) -> FsFuture<'a, ()> {
notimplemented_fut!("copy")
}
#[doc(hidden)]
#[allow(unused_variables)]
fn set_accessed<'a>(
&'a self,
path: &'a DavPath,
tm: SystemTime,
credentials: &C,
) -> FsFuture<'a, ()> {
notimplemented_fut!("set_accessed")
}
#[doc(hidden)]
#[allow(unused_variables)]
fn set_modified<'a>(
&'a self,
path: &'a DavPath,
tm: SystemTime,
credentials: &'a C,
) -> FsFuture<'a, ()> {
notimplemented_fut!("set_mofified")
}
#[allow(unused_variables)]
fn have_props<'a>(
&'a self,
path: &'a DavPath,
credentials: &'a C,
) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
Box::pin(future::ready(false))
}
#[allow(unused_variables)]
fn patch_props<'a>(
&'a self,
path: &'a DavPath,
patch: Vec<(bool, DavProp)>,
credentials: &'a C,
) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
notimplemented_fut!("patch_props")
}
#[allow(unused_variables)]
fn get_props<'a>(
&'a self,
path: &'a DavPath,
do_content: bool,
credentials: &'a C,
) -> FsFuture<'a, Vec<DavProp>> {
notimplemented_fut!("get_props")
}
#[allow(unused_variables)]
fn get_prop<'a>(
&'a self,
path: &'a DavPath,
prop: DavProp,
credentials: &'a C,
) -> FsFuture<'a, Vec<u8>> {
notimplemented_fut!("get_prop")
}
#[allow(unused_variables)]
fn get_quota<'a>(&'a self, credentials: &'a C) -> FsFuture<'a, (u64, Option<u64>)> {
notimplemented_fut!("get_quota")
}
}
clone_trait_object! {<C> GuardedFileSystem<C>}
impl<Fs: DavFileSystem + Clone + Send + Sync> GuardedFileSystem<()> for Fs {
fn open<'a>(
&'a self,
path: &'a DavPath,
options: OpenOptions,
_credentials: &(),
) -> FsFuture<'a, Box<dyn DavFile>> {
DavFileSystem::open(self, path, options)
}
fn read_dir<'a>(
&'a self,
path: &'a DavPath,
meta: ReadDirMeta,
_credentials: &(),
) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
DavFileSystem::read_dir(self, path, meta)
}
fn metadata<'a>(
&'a self,
path: &'a DavPath,
_credentials: &(),
) -> FsFuture<'a, Box<dyn DavMetaData>> {
DavFileSystem::metadata(self, path)
}
fn symlink_metadata<'a>(
&'a self,
path: &'a DavPath,
_credentials: &(),
) -> FsFuture<'a, Box<dyn DavMetaData>> {
DavFileSystem::symlink_metadata(self, path)
}
fn create_dir<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
DavFileSystem::create_dir(self, path)
}
fn remove_dir<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
DavFileSystem::remove_dir(self, path)
}
fn remove_file<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
DavFileSystem::remove_file(self, path)
}
fn rename<'a>(
&'a self,
from: &'a DavPath,
to: &'a DavPath,
_credentials: &(),
) -> FsFuture<'a, ()> {
DavFileSystem::rename(self, from, to)
}
fn copy<'a>(
&'a self,
from: &'a DavPath,
to: &'a DavPath,
_credentials: &(),
) -> FsFuture<'a, ()> {
DavFileSystem::copy(self, from, to)
}
fn set_accessed<'a>(
&'a self,
path: &'a DavPath,
tm: SystemTime,
_credentials: &(),
) -> FsFuture<'a, ()> {
DavFileSystem::set_accessed(self, path, tm)
}
fn set_modified<'a>(
&'a self,
path: &'a DavPath,
tm: SystemTime,
_credentials: &(),
) -> FsFuture<'a, ()> {
DavFileSystem::set_modified(self, path, tm)
}
fn have_props<'a>(
&'a self,
path: &'a DavPath,
_credentials: &(),
) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
DavFileSystem::have_props(self, path)
}
fn patch_props<'a>(
&'a self,
path: &'a DavPath,
patch: Vec<(bool, DavProp)>,
_credentials: &(),
) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
DavFileSystem::patch_props(self, path, patch)
}
fn get_props<'a>(
&'a self,
path: &'a DavPath,
do_content: bool,
_credentials: &(),
) -> FsFuture<'a, Vec<DavProp>> {
DavFileSystem::get_props(self, path, do_content)
}
fn get_prop<'a>(
&'a self,
path: &'a DavPath,
prop: DavProp,
_credentials: &(),
) -> FsFuture<'a, Vec<u8>> {
DavFileSystem::get_prop(self, path, prop)
}
fn get_quota(&'_ self, _credentials: &()) -> FsFuture<'_, (u64, Option<u64>)> {
DavFileSystem::get_quota(self)
}
}
pub trait DavDirEntry: Send + Sync {
fn name(&self) -> Vec<u8>;
fn metadata(&'_ self) -> FsFuture<'_, Box<dyn DavMetaData>>;
fn is_dir(&'_ self) -> FsFuture<'_, bool> {
Box::pin(
self.metadata()
.and_then(|meta| future::ready(Ok(meta.is_dir()))),
)
}
fn is_file(&'_ self) -> FsFuture<'_, bool> {
Box::pin(
self.metadata()
.and_then(|meta| future::ready(Ok(meta.is_file()))),
)
}
fn is_symlink(&'_ self) -> FsFuture<'_, bool> {
Box::pin(
self.metadata()
.and_then(|meta| future::ready(Ok(meta.is_symlink()))),
)
}
}
pub trait DavFile: Debug + Send + Sync {
fn metadata(&'_ mut self) -> FsFuture<'_, Box<dyn DavMetaData>>;
fn write_buf(&'_ mut self, buf: Box<dyn bytes::Buf + Send>) -> FsFuture<'_, ()>;
fn write_bytes(&'_ mut self, buf: bytes::Bytes) -> FsFuture<'_, ()>;
fn read_bytes(&'_ mut self, count: usize) -> FsFuture<'_, bytes::Bytes>;
fn seek(&'_ mut self, pos: SeekFrom) -> FsFuture<'_, u64>;
fn flush(&'_ mut self) -> FsFuture<'_, ()>;
fn redirect_url(&'_ mut self) -> FsFuture<'_, Option<String>> {
future::ready(Ok(None)).boxed()
}
}
pub trait DavMetaData: Debug + Send + Sync + DynClone {
fn len(&self) -> u64;
fn modified(&self) -> FsResult<SystemTime>;
fn is_dir(&self) -> bool;
#[cfg(feature = "caldav")]
fn is_calendar(&self, path: &DavPath) -> bool;
#[cfg(feature = "carddav")]
fn is_addressbook(&self, path: &DavPath) -> bool;
fn etag(&self) -> Option<String> {
if let Ok(t) = self.modified()
&& let Ok(t) = t.duration_since(UNIX_EPOCH)
{
let t = t.as_secs() * 1000000 + t.subsec_nanos() as u64 / 1000;
let tag = if self.is_file() && self.len() > 0 {
format!("{:x}-{:x}", self.len(), t)
} else {
format!("{t:x}")
};
return Some(tag);
}
None
}
fn is_file(&self) -> bool {
!self.is_dir()
}
fn is_symlink(&self) -> bool {
false
}
fn accessed(&self) -> FsResult<SystemTime> {
notimplemented!("access time")
}
fn created(&self) -> FsResult<SystemTime> {
notimplemented!("creation time")
}
fn status_changed(&self) -> FsResult<SystemTime> {
notimplemented!("status change time")
}
fn executable(&self) -> FsResult<bool> {
notimplemented!("executable")
}
fn is_empty(&self) -> bool {
self.len() == 0
}
}
clone_trait_object! {DavMetaData}
#[derive(Debug, Clone, Default)]
pub struct OpenOptions {
pub read: bool,
pub write: bool,
pub append: bool,
pub truncate: bool,
pub create: bool,
pub create_new: bool,
pub size: Option<u64>,
pub checksum: Option<String>,
}
impl OpenOptions {
#[allow(dead_code)]
pub(crate) fn new() -> OpenOptions {
OpenOptions {
read: false,
write: false,
append: false,
truncate: false,
create: false,
create_new: false,
size: None,
checksum: None,
}
}
pub(crate) fn read() -> OpenOptions {
OpenOptions {
read: true,
write: false,
append: false,
truncate: false,
create: false,
create_new: false,
size: None,
checksum: None,
}
}
pub(crate) fn write() -> OpenOptions {
OpenOptions {
read: false,
write: true,
append: false,
truncate: false,
create: false,
create_new: false,
size: None,
checksum: None,
}
}
}
impl std::error::Error for FsError {
fn description(&self) -> &str {
"DavFileSystem error"
}
fn cause(&self) -> Option<&dyn std::error::Error> {
None
}
}
impl std::fmt::Display for FsError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{self:?}")
}
}