//! Contains the structs and traits that define a filesystem backend.
//!
//! You only need this if you are going to implement your own
//! filesystem backend. Otherwise, just use 'LocalFs' or 'MemFs'.
//!
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)))
    };
}

/// Errors generated by a filesystem implementation.
///
/// These are more result-codes than errors, really.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FsError {
    /// Operation not implemented (501)
    NotImplemented,
    /// Something went wrong (500)
    GeneralFailure,
    /// tried to create something, but it existed (405 / 412) (yes, 405. RFC4918 says so)
    Exists,
    /// File / Directory not found (404)
    NotFound,
    /// Not allowed (403)
    Forbidden,
    /// Out of space (507)
    InsufficientStorage,
    /// Symbolic link loop detected (ELOOP) (508)
    LoopDetected,
    /// The path is too long (ENAMETOOLONG) (414)
    PathTooLong,
    /// The file being PUT is too large (413)
    TooLarge,
    /// Trying to MOVE over a mount boundary (EXDEV) (502)
    IsRemote,
}
/// The Result type.
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() {
            // specific errors.
            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 {
            // not an OS error - must be "not implemented"
            // (e.g. metadata().created() on systems without st_crtime)
            return FsError::NotImplemented;
        }
        // generic mappings for-whatever is left.
        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()
    }
}
/// A webdav property.
#[derive(Debug, Clone)]
pub struct DavProp {
    /// Name of the property.
    pub name: String,
    /// XML prefix.
    pub prefix: Option<String>,
    /// XML namespace.
    pub namespace: Option<String>,
    /// Value of the property as raw XML. Use DavProp::new() to create your custom props
    pub xml: Option<Vec<u8>>,
}

impl DavProp {
    /// Create XML property with name, prefix, namespace and value
    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(),
            ),
        }
    }
}

/// Future returned by almost all of the DavFileSystem methods.
pub type FsFuture<'a, T> = Pin<Box<dyn Future<Output = FsResult<T>> + Send + 'a>>;
/// Convenience alias for a boxed Stream.
pub type FsStream<T> = Pin<Box<dyn Stream<Item = FsResult<T>> + Send>>;

/// Used as argument to the read_dir() method.
/// It is:
///
/// - an optimization hint (the implementation may call metadata() and
///   store the result in the returned directory entry)
/// - a way to get metadata instead of symlink_metadata from
///   the directory entry.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReadDirMeta {
    /// DavDirEntry.metadata() behaves as metadata()
    Data,
    /// DavDirEntry.metadata() behaves as symlink_metadata()
    DataSymlink,
    /// No optimizations, otherwise like DataSymlink.
    None,
}

/// File system without access control.
pub trait DavFileSystem {
    /// Open a file.
    fn open<'a>(
        &'a self,
        path: &'a DavPath,
        options: OpenOptions,
    ) -> FsFuture<'a, Box<dyn DavFile>>;

    /// Lists entries within a directory.
    fn read_dir<'a>(
        &'a self,
        path: &'a DavPath,
        meta: ReadDirMeta,
    ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;

    /// Return the metadata of a file or directory.
    fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>>;

    /// Return the metadata of a file, directory or symbolic link.
    ///
    /// Differs from [`metadata`][Self::metadata] that if the path is a symbolic link,
    /// it return the metadata for the link itself, not for the thing
    /// it points to.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn symlink_metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
        self.metadata(path)
    }

    /// Create a directory.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn create_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
        notimplemented_fut!("create_dir")
    }

    /// Remove a directory.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn remove_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
        notimplemented_fut!("remove_dir")
    }

    /// Remove a file.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn remove_file<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
        notimplemented_fut!("remove_file")
    }

    /// Rename a file or directory.
    ///
    /// Source and destination must be the same type (file/dir).
    /// If the destination already exists and is a file, it
    /// should be replaced. If it is a directory it should give
    /// an error.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn rename<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
        notimplemented_fut!("rename")
    }

    /// Copy a file.
    ///
    /// Should also copy the DAV properties, if properties
    /// are implemented.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn copy<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
        notimplemented_fut!("copy")
    }

    /// Set the access time of a file / directory.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[doc(hidden)]
    #[allow(unused_variables)]
    fn set_accessed<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<'a, ()> {
        notimplemented_fut!("set_accessed")
    }

    /// Set the modified time of a file / directory.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[doc(hidden)]
    #[allow(unused_variables)]
    fn set_modified<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<'a, ()> {
        notimplemented_fut!("set_modified")
    }

    /// Indicator that tells if this filesystem driver supports DAV properties.
    ///
    /// The default implementation returns `false`.
    #[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))
    }

    /// Patch the DAV properties of a node (add/remove props).
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[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")
    }

    /// List/get the DAV properties of a node.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn get_props<'a>(&'a self, path: &'a DavPath, do_content: bool) -> FsFuture<'a, Vec<DavProp>> {
        notimplemented_fut!("get_props")
    }

    /// Get one specific named property of a node.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn get_prop<'a>(&'a self, path: &'a DavPath, prop: DavProp) -> FsFuture<'a, Vec<u8>> {
        notimplemented_fut!("get_prop")
    }

    /// Get quota of this filesystem (used/total space).
    ///
    /// The first value returned is the amount of space used,
    /// the second optional value is the total amount of space
    /// (used + available).
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn get_quota(&'_ self) -> FsFuture<'_, (u64, Option<u64>)> {
        notimplemented_fut!("get_quota")
    }
}

/// File system with access control. Type parameter `C` (credentials) represents
/// the authentication and authorization information required for accessing the file system.
/// This can include various forms of credentials such as:
///
/// - auth tokens,
/// - login and password hash combinations,
/// - encryption keys.
///
/// Additionally, it may encapsulate context, scope or metadata related to the request,
/// such as:
///
/// - a set of file system permissions granted to a user,
/// - regionally-defined access scope.
///
/// All [HTTP security considerations][HTTP security] also [apply][WebDAV security]
/// to WebDAV, so you should follow the same authorization best practices as you would
/// for regular HTTP server.
///
/// If credentials `C` include a username, you may want
/// to [specify it as principal][crate::DavConfig::principal]
/// to the [`DavHandler`][crate::DavHandler] builder so that the locks user requests
/// have a known owner.
///
/// For file systems without access control, implement simpler [`DavFileSystem`] trait,
/// for which there's a blanket implementation of `GuardedFileSystem<()>`.
///
/// See also the [auth example].
///
/// [HTTP security]: https://datatracker.ietf.org/doc/html/rfc2616#section-15
/// [WebDAV security]: https://datatracker.ietf.org/doc/html/rfc4918#section-20
/// [auth example]: https://github.com/messense/dav-server-rs/blob/main/examples/auth.rs
pub trait GuardedFileSystem<C>: Send + Sync + DynClone
where
    C: Clone + Send + Sync + 'static,
{
    /// Open a file.
    fn open<'a>(
        &'a self,
        path: &'a DavPath,
        options: OpenOptions,
        credentials: &'a C,
    ) -> FsFuture<'a, Box<dyn DavFile>>;

    /// Lists entries within a directory.
    fn read_dir<'a>(
        &'a self,
        path: &'a DavPath,
        meta: ReadDirMeta,
        credentials: &'a C,
    ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;

    /// Return the metadata of a file or directory.
    fn metadata<'a>(
        &'a self,
        path: &'a DavPath,
        credentials: &'a C,
    ) -> FsFuture<'a, Box<dyn DavMetaData>>;

    /// Return the metadata of a file, directory or symbolic link.
    ///
    /// Differs from [`metadata`][Self::metadata] that if the path is a symbolic link,
    /// it return the metadata for the link itself, not for the thing
    /// it points to.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn symlink_metadata<'a>(
        &'a self,
        path: &'a DavPath,
        credentials: &'a C,
    ) -> FsFuture<'a, Box<dyn DavMetaData>> {
        self.metadata(path, credentials)
    }

    /// Create a directory.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn create_dir<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
        notimplemented_fut!("create_dir")
    }

    /// Remove a directory.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn remove_dir<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
        notimplemented_fut!("remove_dir")
    }

    /// Remove a file.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn remove_file<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
        notimplemented_fut!("remove_file")
    }

    /// Rename a file or directory.
    ///
    /// Source and destination must be the same type (file/dir).
    /// If the destination already exists and is a file, it
    /// should be replaced. If it is a directory it should give
    /// an error.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn rename<'a>(
        &'a self,
        from: &'a DavPath,
        to: &'a DavPath,
        credentials: &'a C,
    ) -> FsFuture<'a, ()> {
        notimplemented_fut!("rename")
    }

    /// Copy a file.
    ///
    /// Should also copy the DAV properties, if properties
    /// are implemented.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[allow(unused_variables)]
    fn copy<'a>(
        &'a self,
        from: &'a DavPath,
        to: &'a DavPath,
        credentials: &'a C,
    ) -> FsFuture<'a, ()> {
        notimplemented_fut!("copy")
    }

    /// Set the access time of a file / directory.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[doc(hidden)]
    #[allow(unused_variables)]
    fn set_accessed<'a>(
        &'a self,
        path: &'a DavPath,
        tm: SystemTime,
        credentials: &C,
    ) -> FsFuture<'a, ()> {
        notimplemented_fut!("set_accessed")
    }

    /// Set the modified time of a file / directory.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[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")
    }

    /// Indicator that tells if this filesystem driver supports DAV properties.
    ///
    /// The default implementation returns `false`.
    #[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))
    }

    /// Patch the DAV properties of a node (add/remove props).
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[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")
    }

    /// List/get the DAV properties of a node.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[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")
    }

    /// Get one specific named property of a node.
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[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")
    }

    /// Get quota of this filesystem (used/total space).
    ///
    /// The first value returned is the amount of space used,
    /// the second optional value is the total amount of space
    /// (used + available).
    ///
    /// The default implementation returns [`FsError::NotImplemented`].
    #[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)
    }
}

/// One directory entry (or child node).
pub trait DavDirEntry: Send + Sync {
    /// Name of the entry.
    fn name(&self) -> Vec<u8>;

    /// Metadata of the entry.
    fn metadata(&'_ self) -> FsFuture<'_, Box<dyn DavMetaData>>;

    /// Default implementation of `is_dir` just returns `metadata()?.is_dir()`.
    /// Implementations can override this if their `metadata()` method is
    /// expensive and there is a cheaper way to provide the same info
    /// (e.g. dirent.d_type in unix filesystems).
    fn is_dir(&'_ self) -> FsFuture<'_, bool> {
        Box::pin(
            self.metadata()
                .and_then(|meta| future::ready(Ok(meta.is_dir()))),
        )
    }

    /// Likewise. Default: `!is_dir()`.
    fn is_file(&'_ self) -> FsFuture<'_, bool> {
        Box::pin(
            self.metadata()
                .and_then(|meta| future::ready(Ok(meta.is_file()))),
        )
    }

    /// Likewise. Default: `false`.
    fn is_symlink(&'_ self) -> FsFuture<'_, bool> {
        Box::pin(
            self.metadata()
                .and_then(|meta| future::ready(Ok(meta.is_symlink()))),
        )
    }
}

/// A `DavFile` is the equivalent of `std::fs::File`, should be
/// readable/writeable/seekable, and be able to return its metadata.
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()
    }
}

/// File metadata. Basically type, length, and some timestamps.
pub trait DavMetaData: Debug + Send + Sync + DynClone {
    /// Size of the file.
    fn len(&self) -> u64;
    /// `Modified` timestamp.
    fn modified(&self) -> FsResult<SystemTime>;
    /// File or directory (aka collection).
    fn is_dir(&self) -> bool;
    /// Is Calendar collection?
    #[cfg(feature = "caldav")]
    fn is_calendar(&self, path: &DavPath) -> bool;
    /// Is Address Book collection?
    #[cfg(feature = "carddav")]
    fn is_addressbook(&self, path: &DavPath) -> bool;

    /// Simplistic implementation of `etag()`
    ///
    /// Returns a simple etag that basically is `\<length\>-\<timestamp_in_ms\>`
    /// with the numbers in hex. Enough for most implementations.
    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
    }

    /// Is this a file and not a directory. Default: `!is_dir()`.
    fn is_file(&self) -> bool {
        !self.is_dir()
    }

    /// Is this a symbolic link. Default: false.
    fn is_symlink(&self) -> bool {
        false
    }

    /// Last access time. Default: `FsError::NotImplemented`.
    fn accessed(&self) -> FsResult<SystemTime> {
        notimplemented!("access time")
    }

    /// Creation time. Default: `FsError::NotImplemented`.
    fn created(&self) -> FsResult<SystemTime> {
        notimplemented!("creation time")
    }

    /// Inode change time (ctime). Default: `FsError::NotImplemented`.
    fn status_changed(&self) -> FsResult<SystemTime> {
        notimplemented!("status change time")
    }

    /// Is file executable (unix: has "x" mode bit). Default: `FsError::NotImplemented`.
    fn executable(&self) -> FsResult<bool> {
        notimplemented!("executable")
    }

    // Is empty file
    fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

clone_trait_object! {DavMetaData}

/// OpenOptions for `open()`.
#[derive(Debug, Clone, Default)]
pub struct OpenOptions {
    /// open for reading
    pub read: bool,
    /// open for writing
    pub write: bool,
    /// open in write-append mode
    pub append: bool,
    /// truncate file first when writing
    pub truncate: bool,
    /// create file if it doesn't exist
    pub create: bool,
    /// must create new file, fail if it already exists.
    pub create_new: bool,
    /// write file total size
    pub size: Option<u64>,
    /// checksum, owncloud extension
    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:?}")
    }
}
