webdav_server/storage/mod.rs
1/// The storage layer — responsible for all filesystem operations.
2///
3/// This module manages the Content-Addressable Storage (CAS) vault where
4/// all encrypted blobs live on disk. It deliberately knows nothing about
5/// users, sessions, or metadata — that is the model layer's concern.
6///
7/// ## Key responsibilities
8///
9/// - Accepting a raw byte stream and committing it to disk atomically.
10/// - Naming blobs by their BLAKE3 hash (CAS semantics).
11/// - Serving blobs back as file handles for streaming to clients.
12/// - Deleting blobs when their reference count drops to zero.
13///
14/// ## Atomicity guarantee
15///
16/// Every write goes through an internal transaction. The transaction
17/// streams bytes to a `<uuid>.tmp` staging file first, then does an atomic
18/// `rename(2)` into the vault. If anything fails mid-stream, the temp file
19/// is cleaned up and the vault is left untouched.
20pub mod error;
21pub mod file;
22pub mod service;
23pub mod transaction;
24
25use std::error::Error as StdErr;
26
27use bytes::Bytes;
28use futures::Stream;
29
30pub use error::{Error, Result};
31pub use service::Service;
32
33#[cfg(test)]
34mod tests;
35
36/// A bundle that ties together a raw byte stream and its declared size.
37///
38/// This newtype exists to avoid "primitive obsession" — passing `expected_size`
39/// and `stream` as separate parameters to multiple layers of functions is error-prone.
40/// Bundling them enforces that the two always travel together.
41///
42/// The `expected_size` is used to pre-allocate disk space via `fallocate` before
43/// streaming begins, which improves performance on spinning drives and SD cards by
44/// preventing filesystem fragmentation.
45pub struct Payload<S, E>
46where
47 S: Stream<Item = std::result::Result<Bytes, E>> + Unpin,
48 E: Into<Box<dyn StdErr + Send + Sync>>,
49{
50 /// The total number of bytes the client claims it will send.
51 /// This is trusted for pre-allocation but validated at EOF.
52 pub expected_size: u64,
53 /// The raw async stream of byte chunks from the request body.
54 pub stream: S,
55}
56
57impl<S, E> Payload<S, E>
58where
59 S: Stream<Item = std::result::Result<Bytes, E>> + Unpin,
60 E: Into<Box<dyn StdErr + Send + Sync>>,
61{
62 /// Constructs a new `Payload` from an expected size and an async byte stream.
63 ///
64 /// The `expected_size` accepts any type that can be converted to `u64`,
65 /// so passing a `u32` or `usize` directly is fine.
66 pub fn new<T>(expected_size: T, stream: S) -> Self
67 where
68 T: Into<u64>,
69 {
70 Self {
71 expected_size: expected_size.into(),
72 stream,
73 }
74 }
75}