1use std::fmt::Debug;
7use std::io::SeekFrom;
8use std::pin::Pin;
9use std::time::{SystemTime, UNIX_EPOCH};
10
11use dyn_clone::{DynClone, clone_trait_object};
12use futures_util::{Future, FutureExt, Stream, TryFutureExt, future};
13use http::StatusCode;
14
15use crate::davpath::DavPath;
16
17macro_rules! notimplemented {
18 ($method:expr_2021) => {
19 Err(FsError::NotImplemented)
20 };
21}
22
23macro_rules! notimplemented_fut {
24 ($method:expr_2021) => {
25 Box::pin(future::ready(Err(FsError::NotImplemented)))
26 };
27}
28
29#[derive(Debug, Clone, Copy, PartialEq)]
33pub enum FsError {
34 NotImplemented,
36 GeneralFailure,
38 Exists,
40 NotFound,
42 Forbidden,
44 InsufficientStorage,
46 LoopDetected,
48 PathTooLong,
50 TooLarge,
52 IsRemote,
54}
55pub type FsResult<T> = std::result::Result<T, FsError>;
57
58#[cfg(any(feature = "memfs", feature = "localfs"))]
59impl From<&std::io::Error> for FsError {
60 fn from(e: &std::io::Error) -> Self {
61 use std::io::ErrorKind;
62
63 if let Some(errno) = e.raw_os_error() {
64 match errno {
66 #[cfg(unix)]
67 libc::EMLINK | libc::ENOSPC | libc::EDQUOT => return FsError::InsufficientStorage,
68 #[cfg(windows)]
69 libc::EMLINK | libc::ENOSPC => return FsError::InsufficientStorage,
70 libc::EFBIG => return FsError::TooLarge,
71 libc::EACCES | libc::EPERM => return FsError::Forbidden,
72 libc::ENOTEMPTY | libc::EEXIST => return FsError::Exists,
73 libc::ELOOP => return FsError::LoopDetected,
74 libc::ENAMETOOLONG => return FsError::PathTooLong,
75 libc::ENOTDIR => return FsError::Forbidden,
76 libc::EISDIR => return FsError::Forbidden,
77 libc::EROFS => return FsError::Forbidden,
78 libc::ENOENT => return FsError::NotFound,
79 libc::ENOSYS => return FsError::NotImplemented,
80 libc::EXDEV => return FsError::IsRemote,
81 _ => {}
82 }
83 } else {
84 return FsError::NotImplemented;
87 }
88 match e.kind() {
90 ErrorKind::NotFound => FsError::NotFound,
91 ErrorKind::PermissionDenied => FsError::Forbidden,
92 _ => FsError::GeneralFailure,
93 }
94 }
95}
96
97#[cfg(any(feature = "memfs", feature = "localfs"))]
98impl From<std::io::Error> for FsError {
99 fn from(e: std::io::Error) -> Self {
100 (&e).into()
101 }
102}
103#[derive(Debug, Clone)]
105pub struct DavProp {
106 pub name: String,
108 pub prefix: Option<String>,
110 pub namespace: Option<String>,
112 pub xml: Option<Vec<u8>>,
114}
115
116impl DavProp {
117 pub fn new(name: String, prefix: String, namespace: String, value: String) -> DavProp {
119 DavProp {
120 name: name.clone(),
121 prefix: Some(prefix.clone()),
122 namespace: Some(namespace.clone()),
123 xml: Some(
124 format!(
125 "<{prefix}:{name} xmlns:{prefix}=\"{namespace}\">{value}</{prefix}:{name}>"
126 )
127 .into_bytes(),
128 ),
129 }
130 }
131}
132
133pub type FsFuture<'a, T> = Pin<Box<dyn Future<Output = FsResult<T>> + Send + 'a>>;
135pub type FsStream<T> = Pin<Box<dyn Stream<Item = FsResult<T>> + Send>>;
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum ReadDirMeta {
148 Data,
150 DataSymlink,
152 None,
154}
155
156pub trait DavFileSystem {
158 fn open<'a>(
160 &'a self,
161 path: &'a DavPath,
162 options: OpenOptions,
163 ) -> FsFuture<'a, Box<dyn DavFile>>;
164
165 fn read_dir<'a>(
167 &'a self,
168 path: &'a DavPath,
169 meta: ReadDirMeta,
170 ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;
171
172 fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>>;
174
175 #[allow(unused_variables)]
183 fn symlink_metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
184 self.metadata(path)
185 }
186
187 #[allow(unused_variables)]
191 fn create_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
192 notimplemented_fut!("create_dir")
193 }
194
195 #[allow(unused_variables)]
199 fn remove_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
200 notimplemented_fut!("remove_dir")
201 }
202
203 #[allow(unused_variables)]
207 fn remove_file<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
208 notimplemented_fut!("remove_file")
209 }
210
211 #[allow(unused_variables)]
220 fn rename<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
221 notimplemented_fut!("rename")
222 }
223
224 #[allow(unused_variables)]
231 fn copy<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
232 notimplemented_fut!("copy")
233 }
234
235 #[doc(hidden)]
239 #[allow(unused_variables)]
240 fn set_accessed<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<'a, ()> {
241 notimplemented_fut!("set_accessed")
242 }
243
244 #[doc(hidden)]
248 #[allow(unused_variables)]
249 fn set_modified<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<'a, ()> {
250 notimplemented_fut!("set_modified")
251 }
252
253 #[allow(unused_variables)]
257 fn have_props<'a>(
258 &'a self,
259 path: &'a DavPath,
260 ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
261 Box::pin(future::ready(false))
262 }
263
264 #[allow(unused_variables)]
268 fn patch_props<'a>(
269 &'a self,
270 path: &'a DavPath,
271 patch: Vec<(bool, DavProp)>,
272 ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
273 notimplemented_fut!("patch_props")
274 }
275
276 #[allow(unused_variables)]
280 fn get_props<'a>(&'a self, path: &'a DavPath, do_content: bool) -> FsFuture<'a, Vec<DavProp>> {
281 notimplemented_fut!("get_props")
282 }
283
284 #[allow(unused_variables)]
288 fn get_prop<'a>(&'a self, path: &'a DavPath, prop: DavProp) -> FsFuture<'a, Vec<u8>> {
289 notimplemented_fut!("get_prop")
290 }
291
292 #[allow(unused_variables)]
300 fn get_quota(&'_ self) -> FsFuture<'_, (u64, Option<u64>)> {
301 notimplemented_fut!("get_quota")
302 }
303}
304
305pub trait GuardedFileSystem<C>: Send + Sync + DynClone
337where
338 C: Clone + Send + Sync + 'static,
339{
340 fn open<'a>(
342 &'a self,
343 path: &'a DavPath,
344 options: OpenOptions,
345 credentials: &'a C,
346 ) -> FsFuture<'a, Box<dyn DavFile>>;
347
348 fn read_dir<'a>(
350 &'a self,
351 path: &'a DavPath,
352 meta: ReadDirMeta,
353 credentials: &'a C,
354 ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;
355
356 fn metadata<'a>(
358 &'a self,
359 path: &'a DavPath,
360 credentials: &'a C,
361 ) -> FsFuture<'a, Box<dyn DavMetaData>>;
362
363 #[allow(unused_variables)]
371 fn symlink_metadata<'a>(
372 &'a self,
373 path: &'a DavPath,
374 credentials: &'a C,
375 ) -> FsFuture<'a, Box<dyn DavMetaData>> {
376 self.metadata(path, credentials)
377 }
378
379 #[allow(unused_variables)]
383 fn create_dir<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
384 notimplemented_fut!("create_dir")
385 }
386
387 #[allow(unused_variables)]
391 fn remove_dir<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
392 notimplemented_fut!("remove_dir")
393 }
394
395 #[allow(unused_variables)]
399 fn remove_file<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
400 notimplemented_fut!("remove_file")
401 }
402
403 #[allow(unused_variables)]
412 fn rename<'a>(
413 &'a self,
414 from: &'a DavPath,
415 to: &'a DavPath,
416 credentials: &'a C,
417 ) -> FsFuture<'a, ()> {
418 notimplemented_fut!("rename")
419 }
420
421 #[allow(unused_variables)]
428 fn copy<'a>(
429 &'a self,
430 from: &'a DavPath,
431 to: &'a DavPath,
432 credentials: &'a C,
433 ) -> FsFuture<'a, ()> {
434 notimplemented_fut!("copy")
435 }
436
437 #[doc(hidden)]
441 #[allow(unused_variables)]
442 fn set_accessed<'a>(
443 &'a self,
444 path: &'a DavPath,
445 tm: SystemTime,
446 credentials: &C,
447 ) -> FsFuture<'a, ()> {
448 notimplemented_fut!("set_accessed")
449 }
450
451 #[doc(hidden)]
455 #[allow(unused_variables)]
456 fn set_modified<'a>(
457 &'a self,
458 path: &'a DavPath,
459 tm: SystemTime,
460 credentials: &'a C,
461 ) -> FsFuture<'a, ()> {
462 notimplemented_fut!("set_mofified")
463 }
464
465 #[allow(unused_variables)]
469 fn have_props<'a>(
470 &'a self,
471 path: &'a DavPath,
472 credentials: &'a C,
473 ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
474 Box::pin(future::ready(false))
475 }
476
477 #[allow(unused_variables)]
481 fn patch_props<'a>(
482 &'a self,
483 path: &'a DavPath,
484 patch: Vec<(bool, DavProp)>,
485 credentials: &'a C,
486 ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
487 notimplemented_fut!("patch_props")
488 }
489
490 #[allow(unused_variables)]
494 fn get_props<'a>(
495 &'a self,
496 path: &'a DavPath,
497 do_content: bool,
498 credentials: &'a C,
499 ) -> FsFuture<'a, Vec<DavProp>> {
500 notimplemented_fut!("get_props")
501 }
502
503 #[allow(unused_variables)]
507 fn get_prop<'a>(
508 &'a self,
509 path: &'a DavPath,
510 prop: DavProp,
511 credentials: &'a C,
512 ) -> FsFuture<'a, Vec<u8>> {
513 notimplemented_fut!("get_prop")
514 }
515
516 #[allow(unused_variables)]
524 fn get_quota<'a>(&'a self, credentials: &'a C) -> FsFuture<'a, (u64, Option<u64>)> {
525 notimplemented_fut!("get_quota")
526 }
527}
528
529clone_trait_object! {<C> GuardedFileSystem<C>}
530
531impl<Fs: DavFileSystem + Clone + Send + Sync> GuardedFileSystem<()> for Fs {
532 fn open<'a>(
533 &'a self,
534 path: &'a DavPath,
535 options: OpenOptions,
536 _credentials: &(),
537 ) -> FsFuture<'a, Box<dyn DavFile>> {
538 DavFileSystem::open(self, path, options)
539 }
540
541 fn read_dir<'a>(
542 &'a self,
543 path: &'a DavPath,
544 meta: ReadDirMeta,
545 _credentials: &(),
546 ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
547 DavFileSystem::read_dir(self, path, meta)
548 }
549
550 fn metadata<'a>(
551 &'a self,
552 path: &'a DavPath,
553 _credentials: &(),
554 ) -> FsFuture<'a, Box<dyn DavMetaData>> {
555 DavFileSystem::metadata(self, path)
556 }
557
558 fn symlink_metadata<'a>(
559 &'a self,
560 path: &'a DavPath,
561 _credentials: &(),
562 ) -> FsFuture<'a, Box<dyn DavMetaData>> {
563 DavFileSystem::symlink_metadata(self, path)
564 }
565
566 fn create_dir<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
567 DavFileSystem::create_dir(self, path)
568 }
569
570 fn remove_dir<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
571 DavFileSystem::remove_dir(self, path)
572 }
573
574 fn remove_file<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
575 DavFileSystem::remove_file(self, path)
576 }
577
578 fn rename<'a>(
579 &'a self,
580 from: &'a DavPath,
581 to: &'a DavPath,
582 _credentials: &(),
583 ) -> FsFuture<'a, ()> {
584 DavFileSystem::rename(self, from, to)
585 }
586
587 fn copy<'a>(
588 &'a self,
589 from: &'a DavPath,
590 to: &'a DavPath,
591 _credentials: &(),
592 ) -> FsFuture<'a, ()> {
593 DavFileSystem::copy(self, from, to)
594 }
595
596 fn set_accessed<'a>(
597 &'a self,
598 path: &'a DavPath,
599 tm: SystemTime,
600 _credentials: &(),
601 ) -> FsFuture<'a, ()> {
602 DavFileSystem::set_accessed(self, path, tm)
603 }
604
605 fn set_modified<'a>(
606 &'a self,
607 path: &'a DavPath,
608 tm: SystemTime,
609 _credentials: &(),
610 ) -> FsFuture<'a, ()> {
611 DavFileSystem::set_modified(self, path, tm)
612 }
613
614 fn have_props<'a>(
615 &'a self,
616 path: &'a DavPath,
617 _credentials: &(),
618 ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
619 DavFileSystem::have_props(self, path)
620 }
621
622 fn patch_props<'a>(
623 &'a self,
624 path: &'a DavPath,
625 patch: Vec<(bool, DavProp)>,
626 _credentials: &(),
627 ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
628 DavFileSystem::patch_props(self, path, patch)
629 }
630
631 fn get_props<'a>(
632 &'a self,
633 path: &'a DavPath,
634 do_content: bool,
635 _credentials: &(),
636 ) -> FsFuture<'a, Vec<DavProp>> {
637 DavFileSystem::get_props(self, path, do_content)
638 }
639
640 fn get_prop<'a>(
641 &'a self,
642 path: &'a DavPath,
643 prop: DavProp,
644 _credentials: &(),
645 ) -> FsFuture<'a, Vec<u8>> {
646 DavFileSystem::get_prop(self, path, prop)
647 }
648
649 fn get_quota(&'_ self, _credentials: &()) -> FsFuture<'_, (u64, Option<u64>)> {
650 DavFileSystem::get_quota(self)
651 }
652}
653
654pub trait DavDirEntry: Send + Sync {
656 fn name(&self) -> Vec<u8>;
658
659 fn metadata(&'_ self) -> FsFuture<'_, Box<dyn DavMetaData>>;
661
662 fn is_dir(&'_ self) -> FsFuture<'_, bool> {
667 Box::pin(self.metadata().and_then(|meta| future::ok(meta.is_dir())))
668 }
669
670 fn is_file(&'_ self) -> FsFuture<'_, bool> {
672 Box::pin(self.metadata().and_then(|meta| future::ok(meta.is_file())))
673 }
674
675 fn is_symlink(&'_ self) -> FsFuture<'_, bool> {
677 Box::pin(
678 self.metadata()
679 .and_then(|meta| future::ok(meta.is_symlink())),
680 )
681 }
682}
683
684pub trait DavFile: Debug + Send + Sync {
687 fn metadata(&'_ mut self) -> FsFuture<'_, Box<dyn DavMetaData>>;
688 fn write_buf(&'_ mut self, buf: Box<dyn bytes::Buf + Send>) -> FsFuture<'_, ()>;
689 fn write_bytes(&'_ mut self, buf: bytes::Bytes) -> FsFuture<'_, ()>;
690 fn read_bytes(&'_ mut self, count: usize) -> FsFuture<'_, bytes::Bytes>;
691 fn seek(&'_ mut self, pos: SeekFrom) -> FsFuture<'_, u64>;
692 fn flush(&'_ mut self) -> FsFuture<'_, ()>;
693 fn redirect_url(&'_ mut self) -> FsFuture<'_, Option<String>> {
694 future::ready(Ok(None)).boxed()
695 }
696}
697
698pub trait DavMetaData: Debug + Send + Sync + DynClone {
700 fn len(&self) -> u64;
702 fn modified(&self) -> FsResult<SystemTime>;
704 fn is_dir(&self) -> bool;
706
707 fn etag(&self) -> Option<String> {
712 if let Ok(t) = self.modified()
713 && let Ok(t) = t.duration_since(UNIX_EPOCH)
714 {
715 let t = t.as_secs() * 1000000 + t.subsec_nanos() as u64 / 1000;
716 let tag = if self.is_file() && self.len() > 0 {
717 format!("{:x}-{:x}", self.len(), t)
718 } else {
719 format!("{t:x}")
720 };
721 return Some(tag);
722 }
723 None
724 }
725
726 fn is_file(&self) -> bool {
728 !self.is_dir()
729 }
730
731 fn is_symlink(&self) -> bool {
733 false
734 }
735
736 fn accessed(&self) -> FsResult<SystemTime> {
738 notimplemented!("access time")
739 }
740
741 fn created(&self) -> FsResult<SystemTime> {
743 notimplemented!("creation time")
744 }
745
746 fn status_changed(&self) -> FsResult<SystemTime> {
748 notimplemented!("status change time")
749 }
750
751 fn executable(&self) -> FsResult<bool> {
753 notimplemented!("executable")
754 }
755
756 fn is_empty(&self) -> bool {
758 self.len() == 0
759 }
760}
761
762clone_trait_object! {DavMetaData}
763
764#[derive(Debug, Clone, Default)]
766pub struct OpenOptions {
767 pub read: bool,
769 pub write: bool,
771 pub append: bool,
773 pub truncate: bool,
775 pub create: bool,
777 pub create_new: bool,
779 pub size: Option<u64>,
781 pub checksum: Option<String>,
783}
784
785impl OpenOptions {
786 #[allow(dead_code)]
787 pub(crate) fn new() -> OpenOptions {
788 OpenOptions {
789 read: false,
790 write: false,
791 append: false,
792 truncate: false,
793 create: false,
794 create_new: false,
795 size: None,
796 checksum: None,
797 }
798 }
799
800 pub(crate) fn read() -> OpenOptions {
801 OpenOptions {
802 read: true,
803 write: false,
804 append: false,
805 truncate: false,
806 create: false,
807 create_new: false,
808 size: None,
809 checksum: None,
810 }
811 }
812
813 pub(crate) fn write() -> OpenOptions {
814 OpenOptions {
815 read: false,
816 write: true,
817 append: false,
818 truncate: false,
819 create: false,
820 create_new: false,
821 size: None,
822 checksum: None,
823 }
824 }
825}
826
827impl std::error::Error for FsError {
828 fn description(&self) -> &str {
829 "DavFileSystem error"
830 }
831 fn cause(&self) -> Option<&dyn std::error::Error> {
832 None
833 }
834}
835
836impl std::fmt::Display for FsError {
837 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
838 write!(f, "{self:?}")
839 }
840}