1use std::fmt::Debug;
7use std::future::{self, Future};
8use std::io::SeekFrom;
9use std::pin::Pin;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12use dyn_clone::{DynClone, clone_trait_object};
13use futures_util::{FutureExt, Stream, TryFutureExt};
14use http::StatusCode;
15
16use crate::davpath::DavPath;
17
18macro_rules! notimplemented {
19 ($method:expr_2021) => {
20 Err(FsError::NotImplemented)
21 };
22}
23
24macro_rules! notimplemented_fut {
25 ($method:expr_2021) => {
26 Box::pin(future::ready(Err(FsError::NotImplemented)))
27 };
28}
29
30#[derive(Debug, Clone, Copy, PartialEq)]
34pub enum FsError {
35 NotImplemented,
37 GeneralFailure,
39 Exists,
41 NotFound,
43 Forbidden,
45 InsufficientStorage,
47 LoopDetected,
49 PathTooLong,
51 TooLarge,
53 IsRemote,
55}
56pub type FsResult<T> = std::result::Result<T, FsError>;
58
59#[cfg(any(feature = "memfs", feature = "localfs"))]
60impl From<&std::io::Error> for FsError {
61 fn from(e: &std::io::Error) -> Self {
62 use std::io::ErrorKind;
63
64 if let Some(errno) = e.raw_os_error() {
65 match errno {
67 #[cfg(unix)]
68 libc::EMLINK | libc::ENOSPC | libc::EDQUOT => return FsError::InsufficientStorage,
69 #[cfg(windows)]
70 libc::EMLINK | libc::ENOSPC => return FsError::InsufficientStorage,
71 libc::EFBIG => return FsError::TooLarge,
72 libc::EACCES | libc::EPERM => return FsError::Forbidden,
73 libc::ENOTEMPTY | libc::EEXIST => return FsError::Exists,
74 libc::ELOOP => return FsError::LoopDetected,
75 libc::ENAMETOOLONG => return FsError::PathTooLong,
76 libc::ENOTDIR => return FsError::Forbidden,
77 libc::EISDIR => return FsError::Forbidden,
78 libc::EROFS => return FsError::Forbidden,
79 libc::ENOENT => return FsError::NotFound,
80 libc::ENOSYS => return FsError::NotImplemented,
81 libc::EXDEV => return FsError::IsRemote,
82 _ => {}
83 }
84 } else {
85 return FsError::NotImplemented;
88 }
89 match e.kind() {
91 ErrorKind::NotFound => FsError::NotFound,
92 ErrorKind::PermissionDenied => FsError::Forbidden,
93 _ => FsError::GeneralFailure,
94 }
95 }
96}
97
98#[cfg(any(feature = "memfs", feature = "localfs"))]
99impl From<std::io::Error> for FsError {
100 fn from(e: std::io::Error) -> Self {
101 (&e).into()
102 }
103}
104#[derive(Debug, Clone)]
106pub struct DavProp {
107 pub name: String,
109 pub prefix: Option<String>,
111 pub namespace: Option<String>,
113 pub xml: Option<Vec<u8>>,
115}
116
117impl DavProp {
118 pub fn new(name: String, prefix: String, namespace: String, value: String) -> DavProp {
120 DavProp {
121 name: name.clone(),
122 prefix: Some(prefix.clone()),
123 namespace: Some(namespace.clone()),
124 xml: Some(
125 format!(
126 "<{prefix}:{name} xmlns:{prefix}=\"{namespace}\">{value}</{prefix}:{name}>"
127 )
128 .into_bytes(),
129 ),
130 }
131 }
132}
133
134pub type FsFuture<'a, T> = Pin<Box<dyn Future<Output = FsResult<T>> + Send + 'a>>;
136pub type FsStream<T> = Pin<Box<dyn Stream<Item = FsResult<T>> + Send>>;
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum ReadDirMeta {
149 Data,
151 DataSymlink,
153 None,
155}
156
157pub trait DavFileSystem {
159 fn open<'a>(
161 &'a self,
162 path: &'a DavPath,
163 options: OpenOptions,
164 ) -> FsFuture<'a, Box<dyn DavFile>>;
165
166 fn read_dir<'a>(
168 &'a self,
169 path: &'a DavPath,
170 meta: ReadDirMeta,
171 ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;
172
173 fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>>;
175
176 #[allow(unused_variables)]
184 fn symlink_metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
185 self.metadata(path)
186 }
187
188 #[allow(unused_variables)]
192 fn create_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
193 notimplemented_fut!("create_dir")
194 }
195
196 #[allow(unused_variables)]
200 fn remove_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
201 notimplemented_fut!("remove_dir")
202 }
203
204 #[allow(unused_variables)]
208 fn remove_file<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
209 notimplemented_fut!("remove_file")
210 }
211
212 #[allow(unused_variables)]
221 fn rename<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
222 notimplemented_fut!("rename")
223 }
224
225 #[allow(unused_variables)]
232 fn copy<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
233 notimplemented_fut!("copy")
234 }
235
236 #[doc(hidden)]
240 #[allow(unused_variables)]
241 fn set_accessed<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<'a, ()> {
242 notimplemented_fut!("set_accessed")
243 }
244
245 #[doc(hidden)]
249 #[allow(unused_variables)]
250 fn set_modified<'a>(&'a self, path: &'a DavPath, tm: SystemTime) -> FsFuture<'a, ()> {
251 notimplemented_fut!("set_modified")
252 }
253
254 #[allow(unused_variables)]
258 fn have_props<'a>(
259 &'a self,
260 path: &'a DavPath,
261 ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
262 Box::pin(future::ready(false))
263 }
264
265 #[allow(unused_variables)]
269 fn patch_props<'a>(
270 &'a self,
271 path: &'a DavPath,
272 patch: Vec<(bool, DavProp)>,
273 ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
274 notimplemented_fut!("patch_props")
275 }
276
277 #[allow(unused_variables)]
281 fn get_props<'a>(&'a self, path: &'a DavPath, do_content: bool) -> FsFuture<'a, Vec<DavProp>> {
282 notimplemented_fut!("get_props")
283 }
284
285 #[allow(unused_variables)]
289 fn get_prop<'a>(&'a self, path: &'a DavPath, prop: DavProp) -> FsFuture<'a, Vec<u8>> {
290 notimplemented_fut!("get_prop")
291 }
292
293 #[allow(unused_variables)]
301 fn get_quota(&'_ self) -> FsFuture<'_, (u64, Option<u64>)> {
302 notimplemented_fut!("get_quota")
303 }
304}
305
306pub trait GuardedFileSystem<C>: Send + Sync + DynClone
338where
339 C: Clone + Send + Sync + 'static,
340{
341 fn open<'a>(
343 &'a self,
344 path: &'a DavPath,
345 options: OpenOptions,
346 credentials: &'a C,
347 ) -> FsFuture<'a, Box<dyn DavFile>>;
348
349 fn read_dir<'a>(
351 &'a self,
352 path: &'a DavPath,
353 meta: ReadDirMeta,
354 credentials: &'a C,
355 ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;
356
357 fn metadata<'a>(
359 &'a self,
360 path: &'a DavPath,
361 credentials: &'a C,
362 ) -> FsFuture<'a, Box<dyn DavMetaData>>;
363
364 #[allow(unused_variables)]
372 fn symlink_metadata<'a>(
373 &'a self,
374 path: &'a DavPath,
375 credentials: &'a C,
376 ) -> FsFuture<'a, Box<dyn DavMetaData>> {
377 self.metadata(path, credentials)
378 }
379
380 #[allow(unused_variables)]
384 fn create_dir<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
385 notimplemented_fut!("create_dir")
386 }
387
388 #[allow(unused_variables)]
392 fn remove_dir<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
393 notimplemented_fut!("remove_dir")
394 }
395
396 #[allow(unused_variables)]
400 fn remove_file<'a>(&'a self, path: &'a DavPath, credentials: &'a C) -> FsFuture<'a, ()> {
401 notimplemented_fut!("remove_file")
402 }
403
404 #[allow(unused_variables)]
413 fn rename<'a>(
414 &'a self,
415 from: &'a DavPath,
416 to: &'a DavPath,
417 credentials: &'a C,
418 ) -> FsFuture<'a, ()> {
419 notimplemented_fut!("rename")
420 }
421
422 #[allow(unused_variables)]
429 fn copy<'a>(
430 &'a self,
431 from: &'a DavPath,
432 to: &'a DavPath,
433 credentials: &'a C,
434 ) -> FsFuture<'a, ()> {
435 notimplemented_fut!("copy")
436 }
437
438 #[doc(hidden)]
442 #[allow(unused_variables)]
443 fn set_accessed<'a>(
444 &'a self,
445 path: &'a DavPath,
446 tm: SystemTime,
447 credentials: &C,
448 ) -> FsFuture<'a, ()> {
449 notimplemented_fut!("set_accessed")
450 }
451
452 #[doc(hidden)]
456 #[allow(unused_variables)]
457 fn set_modified<'a>(
458 &'a self,
459 path: &'a DavPath,
460 tm: SystemTime,
461 credentials: &'a C,
462 ) -> FsFuture<'a, ()> {
463 notimplemented_fut!("set_mofified")
464 }
465
466 #[allow(unused_variables)]
470 fn have_props<'a>(
471 &'a self,
472 path: &'a DavPath,
473 credentials: &'a C,
474 ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
475 Box::pin(future::ready(false))
476 }
477
478 #[allow(unused_variables)]
482 fn patch_props<'a>(
483 &'a self,
484 path: &'a DavPath,
485 patch: Vec<(bool, DavProp)>,
486 credentials: &'a C,
487 ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
488 notimplemented_fut!("patch_props")
489 }
490
491 #[allow(unused_variables)]
495 fn get_props<'a>(
496 &'a self,
497 path: &'a DavPath,
498 do_content: bool,
499 credentials: &'a C,
500 ) -> FsFuture<'a, Vec<DavProp>> {
501 notimplemented_fut!("get_props")
502 }
503
504 #[allow(unused_variables)]
508 fn get_prop<'a>(
509 &'a self,
510 path: &'a DavPath,
511 prop: DavProp,
512 credentials: &'a C,
513 ) -> FsFuture<'a, Vec<u8>> {
514 notimplemented_fut!("get_prop")
515 }
516
517 #[allow(unused_variables)]
525 fn get_quota<'a>(&'a self, credentials: &'a C) -> FsFuture<'a, (u64, Option<u64>)> {
526 notimplemented_fut!("get_quota")
527 }
528}
529
530clone_trait_object! {<C> GuardedFileSystem<C>}
531
532impl<Fs: DavFileSystem + Clone + Send + Sync> GuardedFileSystem<()> for Fs {
533 fn open<'a>(
534 &'a self,
535 path: &'a DavPath,
536 options: OpenOptions,
537 _credentials: &(),
538 ) -> FsFuture<'a, Box<dyn DavFile>> {
539 DavFileSystem::open(self, path, options)
540 }
541
542 fn read_dir<'a>(
543 &'a self,
544 path: &'a DavPath,
545 meta: ReadDirMeta,
546 _credentials: &(),
547 ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
548 DavFileSystem::read_dir(self, path, meta)
549 }
550
551 fn metadata<'a>(
552 &'a self,
553 path: &'a DavPath,
554 _credentials: &(),
555 ) -> FsFuture<'a, Box<dyn DavMetaData>> {
556 DavFileSystem::metadata(self, path)
557 }
558
559 fn symlink_metadata<'a>(
560 &'a self,
561 path: &'a DavPath,
562 _credentials: &(),
563 ) -> FsFuture<'a, Box<dyn DavMetaData>> {
564 DavFileSystem::symlink_metadata(self, path)
565 }
566
567 fn create_dir<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
568 DavFileSystem::create_dir(self, path)
569 }
570
571 fn remove_dir<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
572 DavFileSystem::remove_dir(self, path)
573 }
574
575 fn remove_file<'a>(&'a self, path: &'a DavPath, _credentials: &()) -> FsFuture<'a, ()> {
576 DavFileSystem::remove_file(self, path)
577 }
578
579 fn rename<'a>(
580 &'a self,
581 from: &'a DavPath,
582 to: &'a DavPath,
583 _credentials: &(),
584 ) -> FsFuture<'a, ()> {
585 DavFileSystem::rename(self, from, to)
586 }
587
588 fn copy<'a>(
589 &'a self,
590 from: &'a DavPath,
591 to: &'a DavPath,
592 _credentials: &(),
593 ) -> FsFuture<'a, ()> {
594 DavFileSystem::copy(self, from, to)
595 }
596
597 fn set_accessed<'a>(
598 &'a self,
599 path: &'a DavPath,
600 tm: SystemTime,
601 _credentials: &(),
602 ) -> FsFuture<'a, ()> {
603 DavFileSystem::set_accessed(self, path, tm)
604 }
605
606 fn set_modified<'a>(
607 &'a self,
608 path: &'a DavPath,
609 tm: SystemTime,
610 _credentials: &(),
611 ) -> FsFuture<'a, ()> {
612 DavFileSystem::set_modified(self, path, tm)
613 }
614
615 fn have_props<'a>(
616 &'a self,
617 path: &'a DavPath,
618 _credentials: &(),
619 ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
620 DavFileSystem::have_props(self, path)
621 }
622
623 fn patch_props<'a>(
624 &'a self,
625 path: &'a DavPath,
626 patch: Vec<(bool, DavProp)>,
627 _credentials: &(),
628 ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
629 DavFileSystem::patch_props(self, path, patch)
630 }
631
632 fn get_props<'a>(
633 &'a self,
634 path: &'a DavPath,
635 do_content: bool,
636 _credentials: &(),
637 ) -> FsFuture<'a, Vec<DavProp>> {
638 DavFileSystem::get_props(self, path, do_content)
639 }
640
641 fn get_prop<'a>(
642 &'a self,
643 path: &'a DavPath,
644 prop: DavProp,
645 _credentials: &(),
646 ) -> FsFuture<'a, Vec<u8>> {
647 DavFileSystem::get_prop(self, path, prop)
648 }
649
650 fn get_quota(&'_ self, _credentials: &()) -> FsFuture<'_, (u64, Option<u64>)> {
651 DavFileSystem::get_quota(self)
652 }
653}
654
655pub trait DavDirEntry: Send + Sync {
657 fn name(&self) -> Vec<u8>;
659
660 fn metadata(&'_ self) -> FsFuture<'_, Box<dyn DavMetaData>>;
662
663 fn is_dir(&'_ self) -> FsFuture<'_, bool> {
668 Box::pin(
669 self.metadata()
670 .and_then(|meta| future::ready(Ok(meta.is_dir()))),
671 )
672 }
673
674 fn is_file(&'_ self) -> FsFuture<'_, bool> {
676 Box::pin(
677 self.metadata()
678 .and_then(|meta| future::ready(Ok(meta.is_file()))),
679 )
680 }
681
682 fn is_symlink(&'_ self) -> FsFuture<'_, bool> {
684 Box::pin(
685 self.metadata()
686 .and_then(|meta| future::ready(Ok(meta.is_symlink()))),
687 )
688 }
689}
690
691pub trait DavFile: Debug + Send + Sync {
694 fn metadata(&'_ mut self) -> FsFuture<'_, Box<dyn DavMetaData>>;
695 fn write_buf(&'_ mut self, buf: Box<dyn bytes::Buf + Send>) -> FsFuture<'_, ()>;
696 fn write_bytes(&'_ mut self, buf: bytes::Bytes) -> FsFuture<'_, ()>;
697 fn read_bytes(&'_ mut self, count: usize) -> FsFuture<'_, bytes::Bytes>;
698 fn seek(&'_ mut self, pos: SeekFrom) -> FsFuture<'_, u64>;
699 fn flush(&'_ mut self) -> FsFuture<'_, ()>;
700 fn redirect_url(&'_ mut self) -> FsFuture<'_, Option<String>> {
701 future::ready(Ok(None)).boxed()
702 }
703}
704
705pub trait DavMetaData: Debug + Send + Sync + DynClone {
707 fn len(&self) -> u64;
709 fn modified(&self) -> FsResult<SystemTime>;
711 fn is_dir(&self) -> bool;
713 #[cfg(feature = "caldav")]
715 fn is_calendar(&self, path: &DavPath) -> bool;
716 #[cfg(feature = "carddav")]
718 fn is_addressbook(&self, path: &DavPath) -> bool;
719
720 fn etag(&self) -> Option<String> {
725 if let Ok(t) = self.modified()
726 && let Ok(t) = t.duration_since(UNIX_EPOCH)
727 {
728 let t = t.as_secs() * 1000000 + t.subsec_nanos() as u64 / 1000;
729 let tag = if self.is_file() && self.len() > 0 {
730 format!("{:x}-{:x}", self.len(), t)
731 } else {
732 format!("{t:x}")
733 };
734 return Some(tag);
735 }
736 None
737 }
738
739 fn is_file(&self) -> bool {
741 !self.is_dir()
742 }
743
744 fn is_symlink(&self) -> bool {
746 false
747 }
748
749 fn accessed(&self) -> FsResult<SystemTime> {
751 notimplemented!("access time")
752 }
753
754 fn created(&self) -> FsResult<SystemTime> {
756 notimplemented!("creation time")
757 }
758
759 fn status_changed(&self) -> FsResult<SystemTime> {
761 notimplemented!("status change time")
762 }
763
764 fn executable(&self) -> FsResult<bool> {
766 notimplemented!("executable")
767 }
768
769 fn is_empty(&self) -> bool {
771 self.len() == 0
772 }
773}
774
775clone_trait_object! {DavMetaData}
776
777#[derive(Debug, Clone, Default)]
779pub struct OpenOptions {
780 pub read: bool,
782 pub write: bool,
784 pub append: bool,
786 pub truncate: bool,
788 pub create: bool,
790 pub create_new: bool,
792 pub size: Option<u64>,
794 pub checksum: Option<String>,
796}
797
798impl OpenOptions {
799 #[allow(dead_code)]
800 pub(crate) fn new() -> OpenOptions {
801 OpenOptions {
802 read: false,
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 read() -> OpenOptions {
814 OpenOptions {
815 read: true,
816 write: false,
817 append: false,
818 truncate: false,
819 create: false,
820 create_new: false,
821 size: None,
822 checksum: None,
823 }
824 }
825
826 pub(crate) fn write() -> OpenOptions {
827 OpenOptions {
828 read: false,
829 write: true,
830 append: false,
831 truncate: false,
832 create: false,
833 create_new: false,
834 size: None,
835 checksum: None,
836 }
837 }
838}
839
840impl std::error::Error for FsError {
841 fn description(&self) -> &str {
842 "DavFileSystem error"
843 }
844 fn cause(&self) -> Option<&dyn std::error::Error> {
845 None
846 }
847}
848
849impl std::fmt::Display for FsError {
850 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
851 write!(f, "{self:?}")
852 }
853}