Skip to main content

libfw_core/
storage.rs

1//! Storage abstraction for `libfw-server`.
2//!
3//! Implement [`StorageBackend`] to plug in any storage — a local
4//! filesystem, object storage, in-memory fixtures, … — behind the same
5//! streaming API. Streams are *pull/push* based so memory stays constant
6//! regardless of file size.
7
8use std::io::Read;
9
10use async_trait::async_trait;
11use serde::{Deserialize, Serialize};
12
13use crate::error::StorageError;
14use crate::metadata::FileMeta;
15use crate::range::RangeSpec;
16
17/// How an upload should open (or resume) its target stream.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum WriteMode {
20    /// Create the file; fail with `AlreadyExists` if present.
21    Create,
22    /// Create or truncate the file.
23    Overwrite,
24    /// Continue writing at `offset`; fail if the file is not exactly
25    /// `offset` bytes yet.
26    Resume { offset: u64 },
27}
28
29/// A directory listing entry.
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct DirEntry {
32    /// Full virtual path (relative to the mounted root).
33    pub path: String,
34    /// Whether this entry is a directory.
35    pub is_dir: bool,
36    /// Byte size (0 for directories).
37    pub size: u64,
38    /// Last-modified unix time.
39    pub mtime: u64,
40}
41
42/// Streaming write handle returned by [`StorageBackend::write_stream`].
43///
44/// Data written goes to a temporary location (typically a temp file)
45/// until [`UploadSink::commit`] atomically renames it into place; a
46/// failed or aborted upload leaves no partial target behind.
47#[async_trait]
48pub trait UploadSink: Send {
49    /// Append `buf` at the sink's current position.
50    async fn write(&mut self, buf: &[u8]) -> Result<(), StorageError>;
51
52    /// Finish the stream, finalize the destination and return its metadata.
53    async fn commit(self: Box<Self>) -> Result<FileMeta, StorageError>;
54
55    /// Discard the temporary data and clean up.
56    async fn abort(self: Box<Self>) -> Result<(), StorageError>;
57}
58
59/// Pluggable storage backend for `libfw-server`.
60#[async_trait]
61pub trait StorageBackend: Send + Sync + 'static {
62    /// Return metadata for `path`, or `None` when it does not exist.
63    async fn file_meta(&self, path: &str) -> Result<Option<FileMeta>, StorageError>;
64
65    /// Open a read stream for `path` restricted to `range`.
66    ///
67    /// The returned reader yields exactly `range.len()` bytes on success.
68    async fn read_stream(
69        &self,
70        path: &str,
71        range: RangeSpec,
72    ) -> Result<Box<dyn Read + Send>, StorageError>;
73
74    /// Open a write stream for `path` according to `mode`.
75    async fn write_stream(&self, path: &str, mode: WriteMode) -> Result<Box<dyn UploadSink>, StorageError>;
76
77    /// List the children of directory `path` (or the mount root when
78    /// `path` is empty).
79    async fn list_dir(&self, path: &str) -> Result<Vec<DirEntry>, StorageError>;
80
81    /// Recursively create `path` (and parents) as a directory.
82    async fn mkdir_all(&self, path: &str) -> Result<(), StorageError>;
83
84    /// Remove `path` (file, or directory recursively).
85    async fn remove(&self, path: &str) -> Result<(), StorageError>;
86}