1use std::collections::HashMap;
4use std::future::Future;
5use std::hash::Hash;
6use std::pin::Pin;
7use std::time::{Duration, SystemTime};
8
9use aster_forge_utils::http_range::HttpByteRange;
10use async_trait::async_trait;
11use bytes::Bytes;
12use futures::Stream;
13use http::StatusCode;
14
15use crate::{DavPath, DavXmlElement};
16
17pub type DavContentStream =
19 Pin<Box<dyn Stream<Item = Result<Bytes, DavBackendError>> + Send + 'static>>;
20
21pub struct DavOpenedDownload {
23 pub stream: DavContentStream,
24 pub expected_length: u64,
25}
26
27impl DavOpenedDownload {
28 #[must_use]
29 pub const fn new(stream: DavContentStream, expected_length: u64) -> Self {
30 Self {
31 stream,
32 expected_length,
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
39pub enum DavDownloadOpenError {
40 #[error(transparent)]
41 Backend(#[from] DavBackendError),
42 #[error("download source length does not match the protocol plan")]
43 LengthMismatch { planned: u64, opened: u64 },
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum DavBackendErrorKind {
49 NotFound,
50 Forbidden,
51 Conflict,
52 AlreadyExists,
53 InsufficientStorage,
54 PayloadTooLarge,
55 Locked,
56 InvalidInput,
57 Unsupported,
58 Internal,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
63#[error("WebDAV backend operation failed: {kind:?}")]
64pub struct DavBackendError {
65 pub kind: DavBackendErrorKind,
66}
67
68impl DavBackendError {
69 #[must_use]
70 pub const fn new(kind: DavBackendErrorKind) -> Self {
71 Self { kind }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
77pub enum FsError {
78 #[error("not found")]
79 NotFound,
80 #[error("forbidden")]
81 Forbidden,
82 #[error("general failure")]
83 GeneralFailure,
84 #[error("already exists")]
85 Exists,
86 #[error("insufficient storage")]
87 InsufficientStorage,
88 #[error("too large")]
89 TooLarge,
90 #[error("bad request")]
91 BadRequest,
92}
93
94impl From<FsError> for DavBackendError {
95 fn from(error: FsError) -> Self {
96 let kind = match error {
97 FsError::NotFound => DavBackendErrorKind::NotFound,
98 FsError::Forbidden => DavBackendErrorKind::Forbidden,
99 FsError::GeneralFailure => DavBackendErrorKind::Internal,
100 FsError::Exists => DavBackendErrorKind::AlreadyExists,
101 FsError::InsufficientStorage => DavBackendErrorKind::InsufficientStorage,
102 FsError::TooLarge => DavBackendErrorKind::PayloadTooLarge,
103 FsError::BadRequest => DavBackendErrorKind::InvalidInput,
104 };
105 Self::new(kind)
106 }
107}
108
109pub type FsResult<T> = Result<T, FsError>;
110pub type FsFuture<'a, T> = Pin<Box<dyn Future<Output = FsResult<T>> + Send + 'a>>;
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
114pub enum DavResourceKind {
115 File,
116 Collection,
117}
118
119#[derive(Debug, Clone, Default, PartialEq, Eq)]
121pub struct DavIfResourceState {
122 pub etag: Option<String>,
123 pub lock_tokens: Vec<String>,
124}
125
126#[async_trait]
128pub trait DavIfStateResolver: Send + Sync {
129 async fn resolve_if_state(&self, path: &DavPath)
130 -> Result<DavIfResourceState, DavBackendError>;
131}
132
133#[derive(Debug, Clone, Default, PartialEq, Eq)]
135pub struct DavMutationCredentials {
136 pub submitted_lock_tokens: Vec<String>,
138}
139
140impl DavMutationCredentials {
141 pub fn merge(&mut self, other: Self) {
143 self.submitted_lock_tokens
144 .extend(other.submitted_lock_tokens);
145 self.submitted_lock_tokens.sort();
146 self.submitted_lock_tokens.dedup();
147 }
148}
149
150#[derive(Debug, Clone, Default, PartialEq, Eq)]
152pub struct DavWriteOptions {
153 pub truncate: bool,
154 pub create: bool,
155 pub create_new: bool,
157 pub expected_length: Option<u64>,
158 pub checksum: Option<String>,
159 pub credentials: DavMutationCredentials,
160}
161
162pub trait DavMetaData: Send + Sync {
164 fn len(&self) -> u64;
165 fn modified(&self) -> FsResult<SystemTime>;
170 fn is_dir(&self) -> bool;
171 fn etag(&self) -> Option<String>;
172 fn content_type(&self) -> Option<&str> {
173 None
174 }
175 fn created(&self) -> FsResult<SystemTime>;
180 fn is_empty(&self) -> bool {
181 self.len() == 0
182 }
183 fn is_file(&self) -> bool {
184 !self.is_dir()
185 }
186}
187
188pub trait DavDirectoryEntry: Send {
193 type Metadata: DavMetaData;
194
195 fn name(&self) -> &[u8];
196 fn metadata(&self) -> &Self::Metadata;
197 fn stable_key(&self) -> &[u8] {
198 self.name()
199 }
200}
201
202#[derive(Debug, Clone, Copy)]
207pub struct DavDirectoryPageRequest<'a, C> {
208 pub path: &'a DavPath,
209 pub cursor: Option<&'a C>,
210 pub maximum_entries: usize,
211}
212
213#[derive(Debug, Clone, PartialEq, Eq)]
219pub struct DavDirectoryPage<E, C> {
220 pub entries: Vec<E>,
221 pub next_cursor: Option<C>,
222}
223
224pub trait DavDirectoryEnumerator: Send + Sync {
226 type Cursor: Eq + Hash + Send + Sync;
227 type Entry: DavDirectoryEntry;
228
229 fn read_directory_page<'a>(
232 &'a self,
233 request: DavDirectoryPageRequest<'a, Self::Cursor>,
234 ) -> impl Future<Output = Result<DavDirectoryPage<Self::Entry, Self::Cursor>, DavBackendError>>
235 + Send
236 + 'a;
237}
238
239pub trait DavDownloadSource: Send + Sync {
241 type Metadata: DavMetaData;
242
243 fn metadata<'a>(
244 &'a self,
245 path: &'a DavPath,
246 ) -> impl Future<Output = Result<Self::Metadata, DavBackendError>> + Send + 'a;
247 fn open_full<'a>(
248 &'a self,
249 path: &'a DavPath,
250 ) -> impl Future<Output = Result<DavOpenedDownload, DavBackendError>> + Send + 'a;
251 fn open_range<'a>(
252 &'a self,
253 path: &'a DavPath,
254 range: HttpByteRange,
255 ) -> impl Future<Output = Result<DavOpenedDownload, DavBackendError>> + Send + 'a;
256}
257
258pub trait DavWriteHandle: Send {
264 fn write_bytes(
265 &mut self,
266 buf: Bytes,
267 ) -> impl Future<Output = Result<(), DavBackendError>> + Send + '_;
268 fn finish(self) -> impl Future<Output = Result<(), DavBackendError>> + Send;
269 fn abort(self) -> impl Future<Output = Result<(), DavBackendError>> + Send;
270}
271
272pub trait DavWriteSystem: Send + Sync {
274 type Handle: DavWriteHandle;
275
276 fn open_write<'a>(
277 &'a self,
278 path: &'a DavPath,
279 options: DavWriteOptions,
280 ) -> impl Future<Output = Result<Self::Handle, DavBackendError>> + Send + 'a;
281}
282
283pub trait DavRandomWriteHandle: Send {
289 fn write_at(
290 &mut self,
291 offset: u64,
292 buf: Bytes,
293 ) -> impl Future<Output = Result<(), DavBackendError>> + Send + '_;
294 fn finish(self) -> impl Future<Output = Result<(), DavBackendError>> + Send;
295 fn abort(self) -> impl Future<Output = Result<(), DavBackendError>> + Send;
296}
297
298pub trait DavRandomWriteSystem: Send + Sync {
300 type Handle: DavRandomWriteHandle;
301
302 fn open_random_write<'a>(
303 &'a self,
304 path: &'a DavPath,
305 options: DavWriteOptions,
306 ) -> impl Future<Output = Result<Self::Handle, DavBackendError>> + Send + 'a;
307}
308
309#[derive(Debug, Clone, PartialEq, Eq)]
311pub struct DavProp {
312 pub name: String,
313 pub prefix: Option<String>,
314 pub namespace: Option<String>,
315 pub xml: Option<Vec<u8>>,
316}
317
318pub trait DavFileSystem: Send + Sync {
320 fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>>;
321 fn create_dir<'a>(
322 &'a self,
323 path: &'a DavPath,
324 credentials: DavMutationCredentials,
325 ) -> FsFuture<'a, ()>;
326 fn remove_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()>;
327 fn remove_file<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()>;
328 fn rename<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()>;
329 fn copy<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()>;
330
331 fn get_quota(&self) -> FsFuture<'_, (u64, Option<u64>)> {
332 Box::pin(async { Ok((0, None)) })
333 }
334
335 fn have_props<'a>(
336 &'a self,
337 _path: &'a DavPath,
338 ) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
339 Box::pin(async { false })
340 }
341
342 fn get_props<'a>(
343 &'a self,
344 _path: &'a DavPath,
345 _do_content: bool,
346 ) -> FsFuture<'a, Vec<DavProp>> {
347 Box::pin(async { Ok(Vec::new()) })
348 }
349
350 fn get_props_many<'a>(
355 &'a self,
356 paths: &'a [DavPath],
357 do_content: bool,
358 ) -> FsFuture<'a, HashMap<DavPath, Vec<DavProp>>> {
359 Box::pin(async move {
360 let mut result = HashMap::with_capacity(paths.len());
361 for path in paths {
362 result.insert(path.clone(), self.get_props(path, do_content).await?);
363 }
364 Ok(result)
365 })
366 }
367
368 fn patch_props<'a>(
369 &'a self,
370 _path: &'a DavPath,
371 _patches: Vec<(bool, DavProp)>,
372 ) -> FsFuture<'a, Vec<(StatusCode, DavProp)>> {
373 Box::pin(async { Ok(Vec::new()) })
374 }
375}
376
377#[derive(Debug, Clone)]
379pub struct DavLock {
380 pub token: String,
381 pub path: Box<DavPath>,
382 pub principal: Option<String>,
383 pub owner: Option<Box<DavXmlElement>>,
384 pub timeout_at: Option<SystemTime>,
385 pub timeout: Option<Duration>,
386 pub shared: bool,
387 pub deep: bool,
388}
389
390pub type LsFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq)]
393pub enum DavLockPreflightError {
394 LimitExceeded,
395 GeneralFailure,
396}
397
398#[derive(Debug, Clone)]
399pub enum DavLockError {
400 Conflict(Box<DavLock>),
401 ParentMissing,
402 TokenMismatch,
403 LimitExceeded,
404 NotFound,
405 Backend,
406}
407
408#[derive(Debug, Clone)]
409pub struct DavLockAcquireResult {
410 pub lock: DavLock,
411 pub resource_existed: bool,
412}
413
414#[derive(Debug)]
416pub struct DavLockAcquireRequest<'a> {
417 pub path: &'a DavPath,
418 pub principal: Option<&'a str>,
419 pub owner: Option<&'a DavXmlElement>,
420 pub timeout: Option<Duration>,
421 pub shared: bool,
422 pub deep: bool,
423 pub credentials: DavMutationCredentials,
424}
425
426pub trait DavLockSystem: Send + Sync {
428 fn prepare_lock(&self, _path: &DavPath) -> LsFuture<'_, Result<(), DavLockPreflightError>> {
429 Box::pin(async { Ok(()) })
430 }
431
432 fn lock<'a>(
433 &'a self,
434 request: DavLockAcquireRequest<'a>,
435 ) -> LsFuture<'a, Result<DavLockAcquireResult, DavLockError>>;
436
437 fn unlock(&self, path: &DavPath, token: &str) -> LsFuture<'_, Result<(), DavLockError>>;
438 fn refresh(
439 &self,
440 path: &DavPath,
441 token: &str,
442 timeout: Option<Duration>,
443 ) -> LsFuture<'_, Result<DavLock, DavLockError>>;
444 fn check(
445 &self,
446 path: &DavPath,
447 principal: Option<&str>,
448 ignore_principal: bool,
449 deep: bool,
450 submitted_tokens: &[String],
451 ) -> LsFuture<'_, Result<(), DavLockError>>;
452 fn discover(&self, path: &DavPath) -> LsFuture<'_, Result<Vec<DavLock>, DavBackendError>>;
453 fn discover_many<'a>(
454 &'a self,
455 paths: &'a [DavPath],
456 ) -> LsFuture<'a, Result<HashMap<DavPath, Vec<DavLock>>, DavBackendError>> {
457 Box::pin(async move {
458 let mut result = HashMap::with_capacity(paths.len());
459 for path in paths {
460 result.insert(path.clone(), self.discover(path).await?);
461 }
462 Ok(result)
463 })
464 }
465 fn conflicting_locks(
466 &self,
467 path: &DavPath,
468 deep: bool,
469 ) -> LsFuture<'_, Result<Vec<DavLock>, DavBackendError>>;
470 fn delete(&self, path: &DavPath) -> LsFuture<'_, Result<(), DavLockError>>;
471}