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 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
326pub 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 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}