any_storage/
any.rs

1use std::borrow::Cow;
2use std::io::Result;
3use std::pin::Pin;
4use std::task::{Context, Poll};
5
6use futures::StreamExt;
7
8#[derive(Clone, Debug, derive_more::From)]
9#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
10#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "lowercase"))]
11#[non_exhaustive]
12pub enum AnyStoreConfig {
13    Http(crate::http::HttpStoreConfig),
14    Local(crate::local::LocalStoreConfig),
15    Noop(crate::noop::NoopStoreConfig),
16    PCloud(crate::pcloud::PCloudStoreConfig),
17}
18
19impl AnyStoreConfig {
20    /// Create a [`AnyStore`] based on the configuration
21    pub fn build(&self) -> std::io::Result<AnyStore> {
22        match self {
23            Self::Http(inner) => inner.build().map(AnyStore::Http),
24            Self::Local(inner) => inner.build().map(AnyStore::Local),
25            Self::Noop(inner) => inner.build().map(AnyStore::Noop),
26            Self::PCloud(inner) => inner.build().map(AnyStore::PCloud),
27        }
28    }
29}
30
31#[derive(Clone, Debug, derive_more::From)]
32#[non_exhaustive]
33pub enum AnyStore {
34    Http(crate::http::HttpStore),
35    Local(crate::local::LocalStore),
36    Noop(crate::noop::NoopStore),
37    PCloud(crate::pcloud::PCloudStore),
38}
39
40impl crate::Store for AnyStore {
41    type File = AnyStoreFile;
42    type Directory = AnyStoreDirectory;
43
44    async fn root(&self) -> Result<Self::Directory> {
45        match self {
46            Self::Http(inner) => inner.root().await.map(AnyStoreDirectory::Http),
47            Self::Local(inner) => inner.root().await.map(AnyStoreDirectory::Local),
48            Self::Noop(inner) => inner.root().await.map(AnyStoreDirectory::Noop),
49            Self::PCloud(inner) => inner.root().await.map(AnyStoreDirectory::PCloud),
50        }
51    }
52
53    async fn get_dir<P: Into<std::path::PathBuf>>(&self, path: P) -> Result<Self::Directory> {
54        match self {
55            Self::Http(inner) => inner.get_dir(path).await.map(AnyStoreDirectory::Http),
56            Self::Local(inner) => inner.get_dir(path).await.map(AnyStoreDirectory::Local),
57            Self::Noop(inner) => inner.get_dir(path).await.map(AnyStoreDirectory::Noop),
58            Self::PCloud(inner) => inner.get_dir(path).await.map(AnyStoreDirectory::PCloud),
59        }
60    }
61
62    async fn get_file<P: Into<std::path::PathBuf>>(&self, path: P) -> Result<Self::File> {
63        match self {
64            Self::Http(inner) => inner.get_file(path).await.map(AnyStoreFile::Http),
65            Self::Local(inner) => inner.get_file(path).await.map(AnyStoreFile::Local),
66            Self::Noop(inner) => inner.get_file(path).await.map(AnyStoreFile::Noop),
67            Self::PCloud(inner) => inner.get_file(path).await.map(AnyStoreFile::PCloud),
68        }
69    }
70}
71
72#[derive(Debug, derive_more::From)]
73#[non_exhaustive]
74pub enum AnyStoreFile {
75    Http(crate::http::HttpStoreFile),
76    Local(crate::local::LocalStoreFile),
77    Noop(crate::noop::NoopStoreFile),
78    PCloud(crate::pcloud::PCloudStoreFile),
79}
80
81impl crate::StoreFile for AnyStoreFile {
82    type FileReader = AnyStoreFileReader;
83    type FileWriter = AnyStoreFileWriter;
84    type Metadata = AnyStoreFileMetadata;
85
86    fn path(&self) -> &std::path::Path {
87        match self {
88            Self::Http(inner) => inner.path(),
89            Self::Local(inner) => inner.path(),
90            Self::Noop(inner) => inner.path(),
91            Self::PCloud(inner) => inner.path(),
92        }
93    }
94
95    async fn exists(&self) -> Result<bool> {
96        match self {
97            Self::Http(inner) => inner.exists().await,
98            Self::Local(inner) => inner.exists().await,
99            Self::Noop(inner) => inner.exists().await,
100            Self::PCloud(inner) => inner.exists().await,
101        }
102    }
103
104    fn filename(&self) -> Option<Cow<'_, str>> {
105        match self {
106            Self::Http(inner) => inner.filename(),
107            Self::Local(inner) => inner.filename(),
108            Self::Noop(inner) => inner.filename(),
109            Self::PCloud(inner) => inner.filename(),
110        }
111    }
112
113    async fn metadata(&self) -> Result<Self::Metadata> {
114        match self {
115            Self::Http(inner) => inner.metadata().await.map(AnyStoreFileMetadata::Http),
116            Self::Local(inner) => inner.metadata().await.map(AnyStoreFileMetadata::Local),
117            Self::Noop(inner) => inner.metadata().await.map(AnyStoreFileMetadata::Noop),
118            Self::PCloud(inner) => inner.metadata().await.map(AnyStoreFileMetadata::PCloud),
119        }
120    }
121
122    async fn read<R: std::ops::RangeBounds<u64>>(&self, range: R) -> Result<Self::FileReader> {
123        match self {
124            Self::Http(inner) => inner.read(range).await.map(AnyStoreFileReader::Http),
125            Self::Local(inner) => inner.read(range).await.map(AnyStoreFileReader::Local),
126            Self::Noop(inner) => inner.read(range).await.map(AnyStoreFileReader::Noop),
127            Self::PCloud(inner) => inner.read(range).await.map(AnyStoreFileReader::PCloud),
128        }
129    }
130
131    async fn write(&self, options: crate::WriteOptions) -> Result<Self::FileWriter> {
132        match self {
133            Self::Http(inner) => inner.write(options).await.map(AnyStoreFileWriter::Http),
134            Self::Local(inner) => inner.write(options).await.map(AnyStoreFileWriter::Local),
135            Self::Noop(inner) => inner.write(options).await.map(AnyStoreFileWriter::Noop),
136            Self::PCloud(inner) => inner.write(options).await.map(AnyStoreFileWriter::PCloud),
137        }
138    }
139
140    async fn delete(&self) -> Result<()> {
141        match self {
142            Self::Http(inner) => inner.delete().await,
143            Self::Local(inner) => inner.delete().await,
144            Self::Noop(inner) => inner.delete().await,
145            Self::PCloud(inner) => inner.delete().await,
146        }
147    }
148}
149
150#[derive(Debug)]
151#[non_exhaustive]
152pub enum AnyStoreFileReader {
153    Http(crate::http::HttpStoreFileReader),
154    Local(crate::local::LocalStoreFileReader),
155    Noop(crate::noop::NoopStoreFileReader),
156    PCloud(crate::pcloud::PCloudStoreFileReader),
157}
158
159impl tokio::io::AsyncRead for AnyStoreFileReader {
160    fn poll_read(
161        self: Pin<&mut Self>,
162        cx: &mut Context<'_>,
163        buf: &mut tokio::io::ReadBuf<'_>,
164    ) -> Poll<Result<()>> {
165        let this = self.get_mut();
166        match this {
167            Self::Http(inner) => Pin::new(inner).poll_read(cx, buf),
168            Self::Local(inner) => Pin::new(inner).poll_read(cx, buf),
169            Self::Noop(inner) => Pin::new(inner).poll_read(cx, buf),
170            Self::PCloud(inner) => Pin::new(inner).poll_read(cx, buf),
171        }
172    }
173}
174
175impl crate::StoreFileReader for AnyStoreFileReader {}
176
177#[derive(Clone, Debug, derive_more::From)]
178#[non_exhaustive]
179pub enum AnyStoreFileMetadata {
180    Http(crate::http::HttpStoreFileMetadata),
181    Local(crate::local::LocalStoreFileMetadata),
182    Noop(crate::noop::NoopStoreFileMetadata),
183    PCloud(crate::pcloud::PCloudStoreFileMetadata),
184}
185
186impl crate::StoreMetadata for AnyStoreFileMetadata {
187    fn created(&self) -> u64 {
188        match self {
189            Self::Http(inner) => inner.created(),
190            Self::Local(inner) => inner.created(),
191            Self::Noop(inner) => inner.created(),
192            Self::PCloud(inner) => inner.created(),
193        }
194    }
195
196    fn modified(&self) -> u64 {
197        match self {
198            Self::Http(inner) => inner.modified(),
199            Self::Local(inner) => inner.modified(),
200            Self::Noop(inner) => inner.modified(),
201            Self::PCloud(inner) => inner.modified(),
202        }
203    }
204
205    fn size(&self) -> u64 {
206        match self {
207            Self::Http(inner) => inner.size(),
208            Self::Local(inner) => inner.size(),
209            Self::Noop(inner) => inner.size(),
210            Self::PCloud(inner) => inner.size(),
211        }
212    }
213
214    fn content_type(&self) -> Option<&str> {
215        match self {
216            Self::Http(inner) => inner.content_type(),
217            Self::Local(inner) => inner.content_type(),
218            Self::Noop(inner) => inner.content_type(),
219            Self::PCloud(inner) => inner.content_type(),
220        }
221    }
222}
223
224#[derive(Debug)]
225#[non_exhaustive]
226pub enum AnyStoreFileWriter {
227    Http(crate::noop::NoopStoreFileWriter),
228    Noop(crate::noop::NoopStoreFileWriter),
229    Local(crate::local::LocalStoreFileWriter),
230    PCloud(crate::pcloud::PCloudStoreFileWriter),
231}
232
233impl tokio::io::AsyncWrite for AnyStoreFileWriter {
234    fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize>> {
235        let this = self.get_mut();
236
237        match this {
238            Self::Http(inner) | Self::Noop(inner) => Pin::new(inner).poll_write(cx, buf),
239            Self::Local(inner) => Pin::new(inner).poll_write(cx, buf),
240            Self::PCloud(inner) => Pin::new(inner).poll_write(cx, buf),
241        }
242    }
243
244    fn poll_flush(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Result<()>> {
245        let this = self.get_mut();
246
247        match this {
248            Self::Http(inner) | Self::Noop(inner) => Pin::new(inner).poll_flush(cx),
249            Self::Local(inner) => Pin::new(inner).poll_flush(cx),
250            Self::PCloud(inner) => Pin::new(inner).poll_flush(cx),
251        }
252    }
253
254    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
255        let this = self.get_mut();
256
257        match this {
258            Self::Http(inner) | Self::Noop(inner) => Pin::new(inner).poll_shutdown(cx),
259            Self::Local(inner) => Pin::new(inner).poll_shutdown(cx),
260            Self::PCloud(inner) => Pin::new(inner).poll_shutdown(cx),
261        }
262    }
263}
264
265impl crate::StoreFileWriter for AnyStoreFileWriter {}
266
267#[derive(Debug, derive_more::From)]
268#[non_exhaustive]
269pub enum AnyStoreDirectory {
270    Http(crate::http::HttpStoreDirectory),
271    Local(crate::local::LocalStoreDirectory),
272    Noop(crate::noop::NoopStoreDirectory),
273    PCloud(crate::pcloud::PCloudStoreDirectory),
274}
275
276impl crate::StoreDirectory for AnyStoreDirectory {
277    type Entry = AnyStoreEntry;
278    type Reader = AnyStoreDirectoryReader;
279
280    fn path(&self) -> &std::path::Path {
281        match self {
282            Self::Http(inner) => inner.path(),
283            Self::Local(inner) => inner.path(),
284            Self::Noop(inner) => inner.path(),
285            Self::PCloud(inner) => inner.path(),
286        }
287    }
288
289    async fn exists(&self) -> Result<bool> {
290        match self {
291            Self::Http(inner) => inner.exists().await,
292            Self::Local(inner) => inner.exists().await,
293            Self::Noop(inner) => inner.exists().await,
294            Self::PCloud(inner) => inner.exists().await,
295        }
296    }
297
298    async fn read(&self) -> Result<Self::Reader> {
299        match self {
300            Self::Http(inner) => inner.read().await.map(AnyStoreDirectoryReader::Http),
301            Self::Local(inner) => inner.read().await.map(AnyStoreDirectoryReader::Local),
302            Self::Noop(inner) => inner.read().await.map(AnyStoreDirectoryReader::Noop),
303            Self::PCloud(inner) => inner.read().await.map(AnyStoreDirectoryReader::PCloud),
304        }
305    }
306
307    async fn delete(&self) -> Result<()> {
308        match self {
309            Self::Http(inner) => inner.delete().await,
310            Self::Local(inner) => inner.delete().await,
311            Self::Noop(inner) => inner.delete().await,
312            Self::PCloud(inner) => inner.delete().await,
313        }
314    }
315
316    async fn delete_recursive(&self) -> Result<()> {
317        match self {
318            Self::Http(inner) => inner.delete_recursive().await,
319            Self::Local(inner) => inner.delete_recursive().await,
320            Self::Noop(inner) => inner.delete_recursive().await,
321            Self::PCloud(inner) => inner.delete_recursive().await,
322        }
323    }
324}
325
326/// Type alias for entries in the store, which can be files or directories.
327pub type AnyStoreEntry = crate::Entry<AnyStoreFile, AnyStoreDirectory>;
328
329impl From<crate::http::HttpStoreEntry> for AnyStoreEntry {
330    fn from(value: crate::http::HttpStoreEntry) -> Self {
331        match value {
332            crate::Entry::File(file) => crate::Entry::File(file.into()),
333            crate::Entry::Directory(directory) => crate::Entry::Directory(directory.into()),
334        }
335    }
336}
337
338impl From<crate::local::LocalStoreEntry> for AnyStoreEntry {
339    fn from(value: crate::local::LocalStoreEntry) -> Self {
340        match value {
341            crate::Entry::File(file) => crate::Entry::File(file.into()),
342            crate::Entry::Directory(directory) => crate::Entry::Directory(directory.into()),
343        }
344    }
345}
346
347impl From<crate::noop::NoopStoreEntry> for AnyStoreEntry {
348    fn from(value: crate::noop::NoopStoreEntry) -> Self {
349        match value {
350            crate::Entry::File(file) => crate::Entry::File(file.into()),
351            crate::Entry::Directory(directory) => crate::Entry::Directory(directory.into()),
352        }
353    }
354}
355
356impl From<crate::pcloud::PCloudStoreEntry> for AnyStoreEntry {
357    fn from(value: crate::pcloud::PCloudStoreEntry) -> Self {
358        match value {
359            crate::Entry::File(file) => crate::Entry::File(file.into()),
360            crate::Entry::Directory(directory) => crate::Entry::Directory(directory.into()),
361        }
362    }
363}
364
365#[derive(Debug)]
366#[non_exhaustive]
367pub enum AnyStoreDirectoryReader {
368    Http(crate::http::HttpStoreDirectoryReader),
369    Local(crate::local::LocalStoreDirectoryReader),
370    Noop(crate::noop::NoopStoreDirectoryReader),
371    PCloud(crate::pcloud::PCloudStoreDirectoryReader),
372}
373
374fn from_poll_entry<E: Into<AnyStoreEntry>>(
375    item: Poll<Option<Result<E>>>,
376) -> Poll<Option<Result<AnyStoreEntry>>> {
377    match item {
378        Poll::Ready(Some(Ok(inner))) => Poll::Ready(Some(Ok(inner.into()))),
379        Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))),
380        Poll::Ready(None) => Poll::Ready(None),
381        Poll::Pending => Poll::Pending,
382    }
383}
384
385impl futures::Stream for AnyStoreDirectoryReader {
386    type Item = Result<AnyStoreEntry>;
387
388    /// Polls for the next directory entry.
389    ///
390    /// This function is used to asynchronously retrieve the next entry in the
391    /// directory.
392    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
393        match self.get_mut() {
394            Self::Http(inner) => from_poll_entry(inner.poll_next_unpin(cx)),
395            Self::Local(inner) => from_poll_entry(inner.poll_next_unpin(cx)),
396            Self::Noop(inner) => from_poll_entry(inner.poll_next_unpin(cx)),
397            Self::PCloud(inner) => from_poll_entry(inner.poll_next_unpin(cx)),
398        }
399    }
400}
401
402impl crate::StoreDirectoryReader<AnyStoreEntry> for AnyStoreDirectoryReader {}
403
404#[cfg(test)]
405mod tests {
406    #[test]
407    #[cfg(feature = "serde")]
408    fn should_parse_config() {
409        let _config: super::AnyStoreConfig = toml::from_str(
410            r#"
411type = "pcloud"
412region = "EU"
413credentials = { username = "username", password = "password" }
414root = "/"
415"#,
416        )
417        .unwrap();
418    }
419}