#[cfg(not(windows))]
use std::os::unix::ffi::OsStrExt;
use std::{
fmt::{Debug, Formatter},
io::SeekFrom,
sync::{Arc, OnceLock},
time::SystemTime,
};
use bytes::{Buf, Bytes};
use dav_server::{
davpath::DavPath,
fs::{
DavDirEntry, DavFile, DavFileSystem, DavMetaData, FsError, FsFuture, FsResult, FsStream,
OpenOptions, ReadDirMeta,
},
};
use futures::FutureExt;
use rustic_core::{
repofile::Node,
vfs::{FilePolicy, OpenFile, Vfs},
};
use tokio::task::spawn_blocking;
use crate::repository::IndexedRepo;
fn now() -> SystemTime {
static NOW: OnceLock<SystemTime> = OnceLock::new();
*NOW.get_or_init(SystemTime::now)
}
struct DavFsInner {
repo: IndexedRepo,
vfs: Vfs,
file_policy: FilePolicy,
}
impl Debug for DavFsInner {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "DavFS")
}
}
#[derive(Debug)]
pub struct WebDavFS {
inner: Arc<DavFsInner>,
}
impl WebDavFS {
pub(crate) fn new(repo: IndexedRepo, vfs: Vfs, file_policy: FilePolicy) -> Self {
let inner = DavFsInner {
repo,
vfs,
file_policy,
};
Self {
inner: Arc::new(inner),
}
}
async fn node_from_path(&self, path: &DavPath) -> Result<Node, FsError> {
let inner = self.inner.clone();
let path = path.as_pathbuf();
spawn_blocking(move || {
inner
.vfs
.node_from_path(&inner.repo, &path)
.map_err(|_| FsError::GeneralFailure)
})
.await
.map_err(|_| FsError::GeneralFailure)?
}
async fn dir_entries_from_path(&self, path: &DavPath) -> Result<Vec<Node>, FsError> {
let inner = self.inner.clone();
let path = path.as_pathbuf();
spawn_blocking(move || {
inner
.vfs
.dir_entries_from_path(&inner.repo, &path)
.map_err(|_| FsError::GeneralFailure)
})
.await
.map_err(|_| FsError::GeneralFailure)?
}
}
impl Clone for WebDavFS {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl DavFileSystem for WebDavFS {
fn metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'_, Box<dyn DavMetaData>> {
self.symlink_metadata(davpath)
}
fn symlink_metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'_, Box<dyn DavMetaData>> {
async move {
let node = self.node_from_path(davpath).await?;
let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(node));
Ok(meta)
}
.boxed()
}
fn read_dir<'a>(
&'a self,
davpath: &'a DavPath,
_meta: ReadDirMeta,
) -> FsFuture<'_, FsStream<Box<dyn DavDirEntry>>> {
async move {
let entries = self.dir_entries_from_path(davpath).await?;
let entry_iter = entries.into_iter().map(|e| {
let entry: Box<dyn DavDirEntry> = Box::new(DavFsDirEntry(e));
Ok(entry)
});
let strm: FsStream<Box<dyn DavDirEntry>> = Box::pin(futures::stream::iter(entry_iter));
Ok(strm)
}
.boxed()
}
fn open<'a>(
&'a self,
path: &'a DavPath,
options: OpenOptions,
) -> FsFuture<'_, Box<dyn DavFile>> {
async move {
if options.write
|| options.append
|| options.truncate
|| options.create
|| options.create_new
{
return Err(FsError::Forbidden);
}
let node = self.node_from_path(path).await?;
if matches!(self.inner.file_policy, FilePolicy::Forbidden) {
return Err(FsError::Forbidden);
}
let inner = self.inner.clone();
let node_copy = node.clone();
let open = spawn_blocking(move || {
inner
.repo
.open_file(&node_copy)
.map_err(|_err| FsError::GeneralFailure)
})
.await
.map_err(|_| FsError::GeneralFailure)??;
let file: Box<dyn DavFile> = Box::new(DavFsFile {
node,
open: Arc::new(open),
fs: self.inner.clone(),
seek: 0,
});
Ok(file)
}
.boxed()
}
}
#[derive(Clone, Debug)]
struct DavFsDirEntry(Node);
impl DavDirEntry for DavFsDirEntry {
fn metadata(&self) -> FsFuture<'_, Box<dyn DavMetaData>> {
async move {
let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(self.0.clone()));
Ok(meta)
}
.boxed()
}
#[cfg(not(windows))]
fn name(&self) -> Vec<u8> {
self.0.name().as_bytes().to_vec()
}
#[cfg(windows)]
fn name(&self) -> Vec<u8> {
self.0.name().to_string_lossy().to_string().into_bytes()
}
}
struct DavFsFile {
node: Node,
open: Arc<OpenFile>,
fs: Arc<DavFsInner>,
seek: usize,
}
impl Debug for DavFsFile {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "DavFile")
}
}
impl DavFile for DavFsFile {
fn metadata(&mut self) -> FsFuture<'_, Box<dyn DavMetaData>> {
async move {
let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(self.node.clone()));
Ok(meta)
}
.boxed()
}
fn write_bytes(&mut self, _buf: Bytes) -> FsFuture<'_, ()> {
async move { Err(FsError::Forbidden) }.boxed()
}
fn write_buf(&mut self, _buf: Box<dyn Buf + Send>) -> FsFuture<'_, ()> {
async move { Err(FsError::Forbidden) }.boxed()
}
fn read_bytes(&mut self, count: usize) -> FsFuture<'_, Bytes> {
let fs = self.fs.clone();
let seek = self.seek;
let open = self.open.clone();
async move {
let data = spawn_blocking(move || {
fs.repo
.read_file_at(&open, seek, count)
.map_err(|_err| FsError::GeneralFailure)
})
.await
.map_err(|_| FsError::GeneralFailure)??;
self.seek += data.len();
Ok(data)
}
.boxed()
}
fn seek(&mut self, pos: SeekFrom) -> FsFuture<'_, u64> {
async move {
match pos {
SeekFrom::Start(start) => {
self.seek = usize::try_from(start).expect("usize overflow should not happen");
}
SeekFrom::Current(delta) => {
self.seek = usize::try_from(
i64::try_from(self.seek).expect("i64 wrapped around") + delta,
)
.expect("usize overflow should not happen");
}
SeekFrom::End(end) => {
self.seek = usize::try_from(
i64::try_from(self.node.meta.size).expect("i64 wrapped around") + end,
)
.expect("usize overflow should not happen");
}
}
Ok(self.seek as u64)
}
.boxed()
}
fn flush(&mut self) -> FsFuture<'_, ()> {
async move { Ok(()) }.boxed()
}
}
#[derive(Clone, Debug)]
struct DavFsMetaData(Node);
impl DavMetaData for DavFsMetaData {
fn len(&self) -> u64 {
self.0.meta.size
}
fn created(&self) -> FsResult<SystemTime> {
Ok(now())
}
fn modified(&self) -> FsResult<SystemTime> {
Ok(self.0.meta.mtime.map_or_else(now, SystemTime::from))
}
fn accessed(&self) -> FsResult<SystemTime> {
Ok(self.0.meta.atime.map_or_else(now, SystemTime::from))
}
fn status_changed(&self) -> FsResult<SystemTime> {
Ok(self.0.meta.ctime.map_or_else(now, SystemTime::from))
}
fn is_dir(&self) -> bool {
self.0.is_dir()
}
fn is_file(&self) -> bool {
self.0.is_file()
}
fn is_symlink(&self) -> bool {
self.0.is_symlink()
}
fn executable(&self) -> FsResult<bool> {
if self.0.is_file() {
let Some(mode) = self.0.meta.mode else {
return Ok(false);
};
return Ok((mode & 0o100) > 0);
}
Err(FsError::NotImplemented)
}
}