any_storage/
lib.rs

1#![doc = include_str!("../readme.md")]
2
3use std::borrow::Cow;
4use std::io::Result;
5use std::ops::RangeBounds;
6use std::path::PathBuf;
7
8use futures::Stream;
9
10/// Module wrapping all the implementations in an enum.
11pub mod any;
12/// Module for HTTP storage implementation.
13pub mod http;
14/// Module for local storage implementation.
15pub mod local;
16/// Module for pCloud storage implementation.
17pub mod pcloud;
18
19/// Utility module for common helper functions.
20pub(crate) mod util;
21
22/// Trait representing a generic storage system.
23pub trait Store {
24    /// Associated type for directories in the storage system.
25    type Directory: StoreDirectory;
26    /// Associated type for files in the storage system.
27    type File: StoreFile;
28
29    /// Returns the root directory of the store.
30    ///
31    /// The root is represented by a default (empty) `PathBuf`.
32    fn root(&self) -> impl Future<Output = Result<Self::Directory>> {
33        self.get_dir(PathBuf::default())
34    }
35
36    /// Retrieves a directory at the specified path.
37    ///
38    /// This method returns a future that resolves to the directory at the given
39    /// path.
40    fn get_dir<P: Into<PathBuf>>(&self, path: P) -> impl Future<Output = Result<Self::Directory>>;
41
42    /// Retrieves a file at the specified path.
43    ///
44    /// This method returns a future that resolves to the file at the given
45    /// path.
46    fn get_file<P: Into<PathBuf>>(&self, path: P) -> impl Future<Output = Result<Self::File>>;
47}
48
49/// Trait representing a directory in the storage system.
50pub trait StoreDirectory {
51    /// Associated type for entries in the directory.
52    type Entry;
53    /// Associated type for the reader that iterates over the directory's
54    /// entries.
55    type Reader: StoreDirectoryReader<Self::Entry>;
56
57    /// Checks if the directory exists.
58    ///
59    /// Returns a future that resolves to `true` if the directory exists,
60    /// otherwise `false`.
61    fn exists(&self) -> impl Future<Output = Result<bool>>;
62
63    /// Reads the contents of the directory.
64    ///
65    /// Returns a future that resolves to a reader for the directory's entries.
66    fn read(&self) -> impl Future<Output = Result<Self::Reader>>;
67}
68
69/// Trait for a reader that streams entries from a directory.
70pub trait StoreDirectoryReader<E>: Stream<Item = Result<E>> + Sized {}
71
72/// Trait representing a file in the storage system.
73pub trait StoreFile {
74    /// Associated type for the reader that reads the file's content.
75    type FileReader: StoreFileReader;
76    /// Associated type for the reader that reads the file's content.
77    type FileWriter: StoreFileWriter;
78    /// Associated type for the metadata associated with the file.
79    type Metadata: StoreMetadata;
80
81    /// Returns the file's name if it exists.
82    ///
83    /// This method returns an `Option` containing the file's name.
84    fn filename(&self) -> Option<Cow<'_, str>>;
85
86    /// Checks if the file exists.
87    ///
88    /// Returns a future that resolves to `true` if the file exists, otherwise
89    /// `false`.
90    fn exists(&self) -> impl Future<Output = Result<bool>>;
91
92    /// Retrieves the metadata of the file.
93    ///
94    /// Returns a future that resolves to the file's metadata (size, creation
95    /// time, etc.).
96    fn metadata(&self) -> impl Future<Output = Result<Self::Metadata>>;
97
98    /// Reads a portion of the file's content, specified by a byte range.
99    ///
100    /// Returns a future that resolves to a reader that can read the specified
101    /// range of the file.
102    fn read<R: RangeBounds<u64>>(&self, range: R)
103    -> impl Future<Output = Result<Self::FileReader>>;
104
105    /// Creates a writer
106    fn write(&self, options: WriteOptions) -> impl Future<Output = Result<Self::FileWriter>>;
107}
108
109#[derive(Clone, Copy, Debug)]
110enum WriteMode {
111    Append,
112    Truncate { offset: u64 },
113}
114
115#[derive(Clone, Debug)]
116pub struct WriteOptions {
117    mode: WriteMode,
118}
119
120impl WriteOptions {
121    pub fn append() -> Self {
122        Self {
123            mode: WriteMode::Append,
124        }
125    }
126
127    pub fn create() -> Self {
128        Self {
129            mode: WriteMode::Truncate { offset: 0 },
130        }
131    }
132
133    pub fn truncate(offset: u64) -> Self {
134        Self {
135            mode: WriteMode::Truncate { offset },
136        }
137    }
138}
139
140/// Trait representing a reader that can asynchronously read the contents of a
141/// file.
142pub trait StoreFileReader: tokio::io::AsyncRead {}
143
144/// Trait representing a writer that can asynchronously write the contents to a
145/// file.
146pub trait StoreFileWriter: tokio::io::AsyncWrite {}
147
148/// Struct for stores that don't support writing
149#[derive(Debug)]
150pub struct NoopFileWriter;
151
152impl StoreFileWriter for NoopFileWriter {}
153
154impl tokio::io::AsyncWrite for NoopFileWriter {
155    fn poll_flush(
156        self: std::pin::Pin<&mut Self>,
157        _cx: &mut std::task::Context<'_>,
158    ) -> std::task::Poll<std::result::Result<(), std::io::Error>> {
159        std::task::Poll::Ready(Err(std::io::Error::new(
160            std::io::ErrorKind::Unsupported,
161            "writer not supported for this store",
162        )))
163    }
164
165    fn poll_shutdown(
166        self: std::pin::Pin<&mut Self>,
167        _cx: &mut std::task::Context<'_>,
168    ) -> std::task::Poll<std::result::Result<(), std::io::Error>> {
169        std::task::Poll::Ready(Err(std::io::Error::new(
170            std::io::ErrorKind::Unsupported,
171            "writer not supported for this store",
172        )))
173    }
174
175    fn poll_write(
176        self: std::pin::Pin<&mut Self>,
177        _cx: &mut std::task::Context<'_>,
178        _buf: &[u8],
179    ) -> std::task::Poll<std::result::Result<usize, std::io::Error>> {
180        std::task::Poll::Ready(Err(std::io::Error::new(
181            std::io::ErrorKind::Unsupported,
182            "writer not supported for this store",
183        )))
184    }
185}
186
187/// Enum representing either a file or a directory entry.
188#[derive(Debug)]
189pub enum Entry<File, Directory> {
190    /// A file entry.
191    File(File),
192    /// A directory entry.
193    Directory(Directory),
194}
195
196impl<File, Directory> Entry<File, Directory> {
197    /// Returns `true` if the entry is a directory.
198    pub fn is_directory(&self) -> bool {
199        matches!(self, Self::Directory(_))
200    }
201
202    /// Returns `true` if the entry is a file.
203    pub fn is_file(&self) -> bool {
204        matches!(self, Self::File(_))
205    }
206
207    /// Returns a reference to the directory if the entry is a directory.
208    pub fn as_directory(&self) -> Option<&Directory> {
209        match self {
210            Self::Directory(inner) => Some(inner),
211            _ => None,
212        }
213    }
214
215    /// Returns a reference to the file if the entry is a file.
216    pub fn as_file(&self) -> Option<&File> {
217        match self {
218            Self::File(inner) => Some(inner),
219            _ => None,
220        }
221    }
222
223    /// Converts the entry into a directory, returning an error if it’s not a
224    /// directory.
225    pub fn into_directory(self) -> std::result::Result<Directory, Self> {
226        match self {
227            Self::Directory(inner) => Ok(inner),
228            other => Err(other),
229        }
230    }
231
232    /// Converts the entry into a file, returning an error if it’s not a file.
233    pub fn into_file(self) -> std::result::Result<File, Self> {
234        match self {
235            Self::File(inner) => Ok(inner),
236            other => Err(other),
237        }
238    }
239}
240
241/// Trait representing the metadata of a file.
242pub trait StoreMetadata {
243    /// Returns the size of the file in bytes.
244    fn size(&self) -> u64;
245
246    /// Returns the creation timestamp of the file (epoch time).
247    fn created(&self) -> u64;
248
249    /// Returns the last modification timestamp of the file (epoch time).
250    fn modified(&self) -> u64;
251}
252
253#[cfg(test)]
254fn enable_tracing() {
255    use tracing_subscriber::layer::SubscriberExt;
256    use tracing_subscriber::util::SubscriberInitExt;
257
258    let _ = tracing_subscriber::registry()
259        .with(
260            tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "INFO".into()),
261        )
262        .with(tracing_subscriber::fmt::layer())
263        .try_init();
264}