1use futures::io::{AsyncRead, AsyncSeek, AsyncWrite};
7use std::io;
8#[cfg(feature = "tokio-fs")]
9use std::io::SeekFrom;
10#[cfg(feature = "tokio-fs")]
11use std::pin::Pin;
12#[cfg(feature = "tokio-fs")]
13use std::task::{Context, Poll};
14#[cfg(feature = "tokio-fs")]
15use tokio_util::compat::{Compat, TokioAsyncReadCompatExt};
16
17#[derive(Debug, Clone, Default)]
23pub struct OpenOptions {
24 flags: u8,
26}
27
28impl OpenOptions {
29 const FLAG_READ: u8 = 1 << 0;
30 const FLAG_WRITE: u8 = 1 << 1;
31 const FLAG_CREATE: u8 = 1 << 2;
32 const FLAG_CREATE_NEW: u8 = 1 << 3;
33 const FLAG_TRUNCATE: u8 = 1 << 4;
34 const FLAG_APPEND: u8 = 1 << 5;
35
36 #[must_use]
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 fn set_flag(mut self, flag: u8, value: bool) -> Self {
43 if value {
44 self.flags |= flag;
45 } else {
46 self.flags &= !flag;
47 }
48 self
49 }
50}
51
52macro_rules! flag_setters {
53 ($($(#[$attr:meta])* $name:ident => $flag:ident),* $(,)?) => {
54 impl OpenOptions {
55 $(
56 $(#[$attr])*
57 #[must_use]
58 pub fn $name(self, value: bool) -> Self {
59 self.set_flag(Self::$flag, value)
60 }
61 )*
62 }
63 };
64}
65
66flag_setters! {
67 read => FLAG_READ,
69 write => FLAG_WRITE,
71 create => FLAG_CREATE,
73 create_new => FLAG_CREATE_NEW,
75 truncate => FLAG_TRUNCATE,
77 append => FLAG_APPEND,
79}
80
81macro_rules! flag_getters {
82 ($($(#[$attr:meta])* $name:ident => $flag:ident),* $(,)?) => {
83 impl OpenOptions {
84 $(
85 $(#[$attr])*
86 #[must_use]
87 pub fn $name(&self) -> bool {
88 self.flags & Self::$flag != 0
89 }
90 )*
91 }
92 };
93}
94
95flag_getters! {
96 is_read => FLAG_READ,
98 is_write => FLAG_WRITE,
100 is_create => FLAG_CREATE,
102 is_create_new => FLAG_CREATE_NEW,
104 is_truncate => FLAG_TRUNCATE,
106 is_append => FLAG_APPEND,
108}
109
110impl OpenOptions {
111 #[must_use]
113 pub fn read_only() -> Self {
114 Self::new().read(true)
115 }
116
117 #[must_use]
119 pub fn create_write() -> Self {
120 Self::new().write(true).create(true).truncate(true)
121 }
122
123 #[must_use]
125 pub fn create_new_write() -> Self {
126 Self::new().write(true).create_new(true)
127 }
128}
129
130pub trait StorageProvider: Clone + Send + Sync + 'static {
134 type File: StorageFile + 'static;
136
137 fn open(
139 &self,
140 path: &str,
141 options: OpenOptions,
142 ) -> impl std::future::Future<Output = io::Result<Self::File>> + Send;
143
144 fn exists(&self, path: &str) -> impl std::future::Future<Output = io::Result<bool>> + Send;
146
147 fn delete(&self, path: &str) -> impl std::future::Future<Output = io::Result<()>> + Send;
149
150 fn rename(
152 &self,
153 from: &str,
154 to: &str,
155 ) -> impl std::future::Future<Output = io::Result<()>> + Send;
156}
157
158pub trait StorageFile: AsyncRead + AsyncWrite + AsyncSeek + Unpin + Send + Sync + 'static {
160 fn sync_all(&self) -> impl std::future::Future<Output = io::Result<()>> + Send;
162
163 fn sync_data(&self) -> impl std::future::Future<Output = io::Result<()>> + Send;
165
166 fn size(&self) -> impl std::future::Future<Output = io::Result<u64>> + Send;
168
169 fn set_len(&self, size: u64) -> impl std::future::Future<Output = io::Result<()>> + Send;
171}
172
173#[cfg(feature = "tokio-fs")]
175#[derive(Debug, Clone, Default)]
176pub struct TokioStorageProvider;
177
178#[cfg(feature = "tokio-fs")]
179impl TokioStorageProvider {
180 #[must_use]
182 pub fn new() -> Self {
183 Self
184 }
185}
186
187#[cfg(feature = "tokio-fs")]
188impl StorageProvider for TokioStorageProvider {
189 type File = TokioStorageFile;
190
191 async fn open(&self, path: &str, options: OpenOptions) -> io::Result<Self::File> {
192 let file = tokio::fs::OpenOptions::new()
193 .read(options.is_read())
194 .write(options.is_write())
195 .create(options.is_create())
196 .create_new(options.is_create_new())
197 .truncate(options.is_truncate())
198 .append(options.is_append())
199 .open(path)
200 .await?;
201 Ok(TokioStorageFile {
202 inner: file.compat(),
203 })
204 }
205
206 async fn exists(&self, path: &str) -> io::Result<bool> {
207 match tokio::fs::metadata(path).await {
208 Ok(_) => Ok(true),
209 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
210 Err(e) => Err(e),
211 }
212 }
213
214 async fn delete(&self, path: &str) -> io::Result<()> {
215 tokio::fs::remove_file(path).await
216 }
217
218 async fn rename(&self, from: &str, to: &str) -> io::Result<()> {
219 tokio::fs::rename(from, to).await
220 }
221}
222
223#[cfg(feature = "tokio-fs")]
232#[derive(Debug)]
233pub struct TokioStorageFile {
234 inner: Compat<tokio::fs::File>,
235}
236
237#[cfg(feature = "tokio-fs")]
238impl StorageFile for TokioStorageFile {
239 async fn sync_all(&self) -> io::Result<()> {
240 self.inner.get_ref().sync_all().await
241 }
242
243 async fn sync_data(&self) -> io::Result<()> {
244 self.inner.get_ref().sync_data().await
245 }
246
247 async fn size(&self) -> io::Result<u64> {
248 let metadata = self.inner.get_ref().metadata().await?;
249 Ok(metadata.len())
250 }
251
252 async fn set_len(&self, size: u64) -> io::Result<()> {
253 self.inner.get_ref().set_len(size).await
254 }
255}
256
257#[cfg(feature = "tokio-fs")]
258impl AsyncRead for TokioStorageFile {
259 fn poll_read(
260 mut self: Pin<&mut Self>,
261 cx: &mut Context<'_>,
262 buf: &mut [u8],
263 ) -> Poll<io::Result<usize>> {
264 Pin::new(&mut self.inner).poll_read(cx, buf)
265 }
266}
267
268#[cfg(feature = "tokio-fs")]
269impl AsyncWrite for TokioStorageFile {
270 fn poll_write(
271 mut self: Pin<&mut Self>,
272 cx: &mut Context<'_>,
273 buf: &[u8],
274 ) -> Poll<io::Result<usize>> {
275 Pin::new(&mut self.inner).poll_write(cx, buf)
276 }
277
278 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
279 Pin::new(&mut self.inner).poll_flush(cx)
280 }
281
282 fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
283 Pin::new(&mut self.inner).poll_close(cx)
284 }
285}
286
287#[cfg(feature = "tokio-fs")]
288impl AsyncSeek for TokioStorageFile {
289 fn poll_seek(
290 mut self: Pin<&mut Self>,
291 cx: &mut Context<'_>,
292 pos: SeekFrom,
293 ) -> Poll<io::Result<u64>> {
294 Pin::new(&mut self.inner).poll_seek(cx, pos)
295 }
296}