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
10pub mod any;
12pub mod http;
14pub mod local;
16pub mod pcloud;
18
19pub(crate) mod util;
21
22pub trait Store {
24 type Directory: StoreDirectory;
26 type File: StoreFile;
28
29 fn root(&self) -> impl Future<Output = Result<Self::Directory>> {
33 self.get_dir(PathBuf::default())
34 }
35
36 fn get_dir<P: Into<PathBuf>>(&self, path: P) -> impl Future<Output = Result<Self::Directory>>;
41
42 fn get_file<P: Into<PathBuf>>(&self, path: P) -> impl Future<Output = Result<Self::File>>;
47}
48
49pub trait StoreDirectory {
51 type Entry;
53 type Reader: StoreDirectoryReader<Self::Entry>;
56
57 fn exists(&self) -> impl Future<Output = Result<bool>>;
62
63 fn read(&self) -> impl Future<Output = Result<Self::Reader>>;
67}
68
69pub trait StoreDirectoryReader<E>: Stream<Item = Result<E>> + Sized {}
71
72pub trait StoreFile {
74 type FileReader: StoreFileReader;
76 type FileWriter: StoreFileWriter;
78 type Metadata: StoreMetadata;
80
81 fn filename(&self) -> Option<Cow<'_, str>>;
85
86 fn exists(&self) -> impl Future<Output = Result<bool>>;
91
92 fn metadata(&self) -> impl Future<Output = Result<Self::Metadata>>;
97
98 fn read<R: RangeBounds<u64>>(&self, range: R)
103 -> impl Future<Output = Result<Self::FileReader>>;
104
105 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
140pub trait StoreFileReader: tokio::io::AsyncRead {}
143
144pub trait StoreFileWriter: tokio::io::AsyncWrite {}
147
148#[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#[derive(Debug)]
189pub enum Entry<File, Directory> {
190 File(File),
192 Directory(Directory),
194}
195
196impl<File, Directory> Entry<File, Directory> {
197 pub fn is_directory(&self) -> bool {
199 matches!(self, Self::Directory(_))
200 }
201
202 pub fn is_file(&self) -> bool {
204 matches!(self, Self::File(_))
205 }
206
207 pub fn as_directory(&self) -> Option<&Directory> {
209 match self {
210 Self::Directory(inner) => Some(inner),
211 _ => None,
212 }
213 }
214
215 pub fn as_file(&self) -> Option<&File> {
217 match self {
218 Self::File(inner) => Some(inner),
219 _ => None,
220 }
221 }
222
223 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 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
241pub trait StoreMetadata {
243 fn size(&self) -> u64;
245
246 fn created(&self) -> u64;
248
249 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}