1use std::collections::HashMap;
4use std::future::Future;
5use std::io::SeekFrom;
6use std::pin::Pin;
7use std::time::{Duration, SystemTime};
8
9use async_trait::async_trait;
10use bytes::{Buf, Bytes};
11use futures::Stream;
12use http::StatusCode;
13
14use crate::{DavPath, DavXmlElement};
15
16pub type DavContentStream =
18 Pin<Box<dyn Stream<Item = Result<Bytes, DavBackendError>> + Send + 'static>>;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum DavBackendErrorKind {
23 NotFound,
24 Forbidden,
25 Conflict,
26 AlreadyExists,
27 InsufficientStorage,
28 PayloadTooLarge,
29 Locked,
30 InvalidInput,
31 Unsupported,
32 Internal,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
37#[error("WebDAV backend operation failed: {kind:?}")]
38pub struct DavBackendError {
39 pub kind: DavBackendErrorKind,
40}
41
42impl DavBackendError {
43 #[must_use]
44 pub const fn new(kind: DavBackendErrorKind) -> Self {
45 Self { kind }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
51pub enum FsError {
52 #[error("not found")]
53 NotFound,
54 #[error("forbidden")]
55 Forbidden,
56 #[error("general failure")]
57 GeneralFailure,
58 #[error("already exists")]
59 Exists,
60 #[error("insufficient storage")]
61 InsufficientStorage,
62 #[error("too large")]
63 TooLarge,
64 #[error("bad request")]
65 BadRequest,
66}
67
68impl From<FsError> for DavBackendError {
69 fn from(error: FsError) -> Self {
70 let kind = match error {
71 FsError::NotFound => DavBackendErrorKind::NotFound,
72 FsError::Forbidden => DavBackendErrorKind::Forbidden,
73 FsError::GeneralFailure => DavBackendErrorKind::Internal,
74 FsError::Exists => DavBackendErrorKind::AlreadyExists,
75 FsError::InsufficientStorage => DavBackendErrorKind::InsufficientStorage,
76 FsError::TooLarge => DavBackendErrorKind::PayloadTooLarge,
77 FsError::BadRequest => DavBackendErrorKind::InvalidInput,
78 };
79 Self::new(kind)
80 }
81}
82
83pub type FsResult<T> = Result<T, FsError>;
84pub type FsFuture<'a, T> = Pin<Box<dyn Future<Output = FsResult<T>> + Send + 'a>>;
85pub type FsStream<T> = Pin<Box<dyn Stream<Item = FsResult<T>> + Send>>;
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
89pub enum DavResourceKind {
90 File,
91 Collection,
92}
93
94#[derive(Debug, Clone, Default, PartialEq, Eq)]
96pub struct DavIfResourceState {
97 pub etag: Option<String>,
98 pub lock_tokens: Vec<String>,
99}
100
101#[async_trait]
103pub trait DavIfStateResolver: Send + Sync {
104 async fn resolve_if_state(&self, path: &DavPath)
105 -> Result<DavIfResourceState, DavBackendError>;
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum ReadDirMeta {
111 Data,
112}
113
114#[derive(Debug, Clone, Default, PartialEq, Eq)]
116pub struct OpenOptions {
117 pub read: bool,
118 pub write: bool,
119 pub append: bool,
120 pub truncate: bool,
121 pub create: bool,
122 pub create_new: bool,
123 pub size: Option<u64>,
124 pub checksum: Option<String>,
125}
126
127impl OpenOptions {
128 #[must_use]
129 pub fn read() -> Self {
130 Self {
131 read: true,
132 ..Self::default()
133 }
134 }
135
136 #[must_use]
137 pub fn write() -> Self {
138 Self {
139 write: true,
140 ..Self::default()
141 }
142 }
143}
144
145pub trait DavMetaData: Send + Sync {
147 fn len(&self) -> u64;
148 fn modified(&self) -> FsResult<SystemTime>;
149 fn is_dir(&self) -> bool;
150 fn etag(&self) -> Option<String>;
151 fn content_type(&self) -> Option<&str> {
152 None
153 }
154 fn created(&self) -> FsResult<SystemTime>;
155 fn is_empty(&self) -> bool {
156 self.len() == 0
157 }
158 fn is_file(&self) -> bool {
159 !self.is_dir()
160 }
161}
162
163pub trait DavDirEntry: Send {
165 fn name(&self) -> Vec<u8>;
166 fn metadata<'a>(&'a self) -> FsFuture<'a, Box<dyn DavMetaData>>;
167}
168
169pub trait DavFile: Send {
171 fn metadata<'a>(&'a mut self) -> FsFuture<'a, Box<dyn DavMetaData>>;
172 fn read_bytes(&mut self, count: usize) -> FsFuture<'_, Bytes>;
173 fn write_bytes(&mut self, buf: Bytes) -> FsFuture<'_, ()>;
174 fn write_buf(&mut self, buf: Box<dyn Buf + Send>) -> FsFuture<'_, ()>;
175 fn seek(&mut self, pos: SeekFrom) -> FsFuture<'_, u64>;
176 fn flush(&mut self) -> FsFuture<'_, ()>;
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct DavProp {
182 pub name: String,
183 pub prefix: Option<String>,
184 pub namespace: Option<String>,
185 pub xml: Option<Vec<u8>>,
186}
187
188pub trait DavFileSystem: Send + Sync {
190 fn open<'a>(
191 &'a self,
192 path: &'a DavPath,
193 options: OpenOptions,
194 ) -> FsFuture<'a, Box<dyn DavFile>>;
195 fn read_dir<'a>(
196 &'a self,
197 path: &'a DavPath,
198 meta: ReadDirMeta,
199 ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>>;
200 fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>>;
201 fn create_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()>;
202 fn remove_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()>;
203 fn remove_file<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()>;
204 fn rename<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()>;
205 fn copy<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()>;
206
207 fn get_quota(&self) -> FsFuture<'_, (u64, Option<u64>)> {
208 Box::pin(async { Ok((0, None)) })
209 }
210
211 fn have_props<'a>(
212 &'a self,
213 _path: &'a DavPath,
214 ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
215 Box::pin(async { false })
216 }
217
218 fn get_props<'a>(
219 &'a self,
220 _path: &'a DavPath,
221 _do_content: bool,
222 ) -> FsFuture<'a, Vec<DavProp>> {
223 Box::pin(async { Ok(Vec::new()) })
224 }
225
226 fn get_props_many<'a>(
231 &'a self,
232 paths: &'a [DavPath],
233 do_content: bool,
234 ) -> FsFuture<'a, HashMap<DavPath, Vec<DavProp>>> {
235 Box::pin(async move {
236 let mut result = HashMap::with_capacity(paths.len());
237 for path in paths {
238 result.insert(path.clone(), self.get_props(path, do_content).await?);
239 }
240 Ok(result)
241 })
242 }
243
244 fn patch_props<'a>(
245 &'a self,
246 _path: &'a DavPath,
247 _patches: Vec<(bool, DavProp)>,
248 ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
249 Box::pin(async { Ok(Vec::new()) })
250 }
251}
252
253#[derive(Debug, Clone)]
255pub struct DavLock {
256 pub token: String,
257 pub path: Box<DavPath>,
258 pub principal: Option<String>,
259 pub owner: Option<Box<DavXmlElement>>,
260 pub timeout_at: Option<SystemTime>,
261 pub timeout: Option<Duration>,
262 pub shared: bool,
263 pub deep: bool,
264}
265
266pub type LsFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum DavLockPreflightError {
270 LimitExceeded,
271 GeneralFailure,
272}
273
274#[derive(Debug, Clone)]
275pub enum DavLockError {
276 Conflict(Box<DavLock>),
277 TokenMismatch,
278 LimitExceeded,
279 Backend,
280}
281
282pub trait DavLockSystem: Send + Sync {
284 fn prepare_lock(&self, _path: &DavPath) -> LsFuture<'_, Result<(), DavLockPreflightError>> {
285 Box::pin(async { Ok(()) })
286 }
287
288 fn lock(
289 &self,
290 path: &DavPath,
291 principal: Option<&str>,
292 owner: Option<&DavXmlElement>,
293 timeout: Option<Duration>,
294 shared: bool,
295 deep: bool,
296 ) -> LsFuture<'_, Result<DavLock, DavLockError>>;
297
298 fn unlock(&self, path: &DavPath, token: &str) -> LsFuture<'_, Result<(), DavLockError>>;
299 fn refresh(
300 &self,
301 path: &DavPath,
302 token: &str,
303 timeout: Option<Duration>,
304 ) -> LsFuture<'_, Result<DavLock, DavLockError>>;
305 fn check(
306 &self,
307 path: &DavPath,
308 principal: Option<&str>,
309 ignore_principal: bool,
310 deep: bool,
311 submitted_tokens: &[String],
312 ) -> LsFuture<'_, Result<(), DavLock>>;
313 fn discover(&self, path: &DavPath) -> LsFuture<'_, Vec<DavLock>>;
314 fn discover_many<'a>(
315 &'a self,
316 paths: &'a [DavPath],
317 ) -> LsFuture<'a, HashMap<DavPath, Vec<DavLock>>> {
318 Box::pin(async move {
319 let mut result = HashMap::with_capacity(paths.len());
320 for path in paths {
321 result.insert(path.clone(), self.discover(path).await);
322 }
323 result
324 })
325 }
326 fn conflicting_locks(&self, path: &DavPath, deep: bool) -> LsFuture<'_, Vec<DavLock>>;
327 fn delete(&self, path: &DavPath) -> LsFuture<'_, Result<(), DavLockError>>;
328}