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