bevy_persistent/
storage.rs

1//! A storage.
2
3use crate::prelude::*;
4
5/// A storage.
6#[derive(Clone, Debug, Eq, PartialEq, Reflect)]
7pub enum Storage {
8    #[cfg(not(target_family = "wasm"))]
9    Filesystem { path: PathBuf },
10    #[cfg(target_family = "wasm")]
11    LocalStorage { key: String },
12    #[cfg(target_family = "wasm")]
13    SessionStorage { key: String },
14}
15
16impl Storage {
17    /// Initializes the storage.
18    pub fn initialize(&self) -> Result<(), PersistenceError> {
19        match self {
20            #[cfg(not(target_family = "wasm"))]
21            Storage::Filesystem { path } => {
22                if let Some(parent) = path.parent() {
23                    std::fs::create_dir_all(parent)?;
24                }
25            },
26            #[cfg(target_family = "wasm")]
27            Storage::LocalStorage { .. } => {},
28            #[cfg(target_family = "wasm")]
29            Storage::SessionStorage { .. } => {},
30        }
31        Ok(())
32    }
33
34    /// Gets if the storage is occupied.
35    pub fn occupied(&self) -> bool {
36        match self {
37            #[cfg(not(target_family = "wasm"))]
38            Storage::Filesystem { path } => path.exists(),
39            #[cfg(target_family = "wasm")]
40            Storage::LocalStorage { key } => {
41                use gloo_storage::{
42                    LocalStorage,
43                    Storage,
44                };
45                matches!(LocalStorage::raw().get_item(key), Ok(Some(_)))
46            },
47            #[cfg(target_family = "wasm")]
48            Storage::SessionStorage { key } => {
49                use gloo_storage::{
50                    SessionStorage,
51                    Storage,
52                };
53                matches!(SessionStorage::raw().get_item(key), Ok(Some(_)))
54            },
55        }
56    }
57
58    /// Reads a resource from the storage.
59    pub fn read<R: Serialize + DeserializeOwned>(
60        &self,
61        name: &str,
62        format: StorageFormat,
63    ) -> Result<R, PersistenceError> {
64        match self {
65            #[cfg(not(target_family = "wasm"))]
66            Storage::Filesystem { path } => {
67                let bytes = std::fs::read(path)?;
68                format.deserialize::<R>(name, &bytes)
69            },
70            #[cfg(target_family = "wasm")]
71            Storage::LocalStorage { key } => {
72                use gloo_storage::{
73                    LocalStorage,
74                    Storage,
75                    errors::StorageError,
76                };
77
78                #[cfg(feature = "json")]
79                if format == StorageFormat::Json {
80                    return Ok(LocalStorage::get::<R>(key).inspect_err(|error| {
81                        if let StorageError::SerdeError(error) = &error {
82                            log::error!("failed to parse {} as JSON\n\n{}", name, error);
83                        }
84                    })?);
85                }
86                #[cfg(all(feature = "json", feature = "pretty"))]
87                if format == StorageFormat::JsonPretty {
88                    return Ok(LocalStorage::get::<R>(key).inspect_err(|error| {
89                        if let StorageError::SerdeError(error) = &error {
90                            log::error!("failed to parse {} as pretty JSON\n\n{}", name, error);
91                        }
92                    })?);
93                }
94
95                #[cfg(feature = "bincode")]
96                if format == StorageFormat::Bincode {
97                    let bytes = LocalStorage::get::<Vec<u8>>(key).inspect_err(|error| {
98                        if let StorageError::SerdeError(error) = &error {
99                            log::error!("failed to get {} as a byte array\n\n{}", name, error);
100                        }
101                    })?;
102                    return format.deserialize::<R>(name, &bytes);
103                }
104
105                let content = LocalStorage::get::<String>(key).inspect_err(|error| {
106                    if let StorageError::SerdeError(error) = &error {
107                        log::error!("failed to get {} as a string\n\n{}", name, error);
108                    }
109                })?;
110                format.deserialize::<R>(name, content.as_bytes())
111            },
112            #[cfg(target_family = "wasm")]
113            Storage::SessionStorage { key } => {
114                use gloo_storage::{
115                    SessionStorage,
116                    Storage,
117                    errors::StorageError,
118                };
119
120                #[cfg(feature = "json")]
121                if format == StorageFormat::Json {
122                    return Ok(SessionStorage::get::<R>(key).inspect_err(|error| {
123                        if let StorageError::SerdeError(error) = &error {
124                            log::error!("failed to parse {} as JSON\n\n{}", name, error);
125                        }
126                    })?);
127                }
128                #[cfg(all(feature = "json", feature = "pretty"))]
129                if format == StorageFormat::JsonPretty {
130                    return Ok(SessionStorage::get::<R>(key).inspect_err(|error| {
131                        if let StorageError::SerdeError(error) = &error {
132                            log::error!("failed to parse {} as pretty JSON\n\n{}", name, error);
133                        }
134                    })?);
135                }
136
137                #[cfg(feature = "bincode")]
138                if format == StorageFormat::Bincode {
139                    let bytes = SessionStorage::get::<Vec<u8>>(key).inspect_err(|error| {
140                        if let StorageError::SerdeError(error) = &error {
141                            log::error!("failed to get {} as a byte array\n\n{}", name, error);
142                        }
143                    })?;
144                    return format.deserialize::<R>(name, &bytes);
145                }
146
147                let content = SessionStorage::get::<String>(key).inspect_err(|error| {
148                    if let StorageError::SerdeError(error) = &error {
149                        log::error!("failed to get {} as a string\n\n{}", name, error);
150                    }
151                })?;
152                format.deserialize::<R>(name, content.as_bytes())
153            },
154        }
155    }
156
157    /// Writes a resource to the storage.
158    pub fn write<R: Serialize + DeserializeOwned>(
159        &self,
160        name: &str,
161        format: StorageFormat,
162        resource: &R,
163    ) -> Result<(), PersistenceError> {
164        match self {
165            #[cfg(not(target_family = "wasm"))]
166            Storage::Filesystem { path } => {
167                let bytes = format.serialize(name, resource)?;
168
169                use std::io::Write;
170                std::fs::OpenOptions::new()
171                    .create(true)
172                    .truncate(true)
173                    .write(true)
174                    .open(path)
175                    .and_then(|mut file| file.write_all(&bytes))?;
176            },
177            #[cfg(target_family = "wasm")]
178            Storage::LocalStorage { key } => {
179                use gloo_storage::{
180                    LocalStorage,
181                    Storage,
182                    errors::StorageError,
183                };
184
185                #[cfg(feature = "json")]
186                if format == StorageFormat::Json {
187                    LocalStorage::set::<&R>(key, resource).inspect_err(|error| {
188                        if let StorageError::SerdeError(error) = &error {
189                            log::error!("failed to serialize {} to JSON\n\n{}", name, error);
190                        }
191                    })?;
192                    return Ok(());
193                }
194                #[cfg(all(feature = "json", feature = "pretty"))]
195                if format == StorageFormat::JsonPretty {
196                    LocalStorage::set::<&R>(key, resource).inspect_err(|error| {
197                        if let StorageError::SerdeError(error) = &error {
198                            log::error!("failed to serialize {} to pretty JSON\n\n{}", name, error);
199                        }
200                    })?;
201                    return Ok(());
202                }
203
204                #[cfg(feature = "bincode")]
205                if format == StorageFormat::Bincode {
206                    let bytes = format.serialize(name, resource)?;
207                    LocalStorage::set::<&[u8]>(key, &bytes)?;
208                    return Ok(());
209                }
210
211                let bytes = format.serialize(name, resource)?;
212
213                // unwrapping is okay in this case because
214                // remaining storage formats all return a string
215                // and that string is converted to bytes
216                let string = std::str::from_utf8(&bytes).unwrap();
217                LocalStorage::set::<&str>(key, string)?;
218            },
219            #[cfg(target_family = "wasm")]
220            Storage::SessionStorage { key } => {
221                use gloo_storage::{
222                    SessionStorage,
223                    Storage,
224                    errors::StorageError,
225                };
226
227                #[cfg(feature = "json")]
228                if format == StorageFormat::Json {
229                    SessionStorage::set::<&R>(key, resource).inspect_err(|error| {
230                        if let StorageError::SerdeError(error) = &error {
231                            log::error!("failed to serialize {} to JSON\n\n{}", name, error);
232                        }
233                    })?;
234                    return Ok(());
235                }
236                #[cfg(all(feature = "json", feature = "pretty"))]
237                if format == StorageFormat::JsonPretty {
238                    SessionStorage::set::<&R>(key, resource).inspect_err(|error| {
239                        if let StorageError::SerdeError(error) = &error {
240                            log::error!("failed to serialize {} to pretty JSON\n\n{}", name, error);
241                        }
242                    })?;
243                    return Ok(());
244                }
245
246                #[cfg(feature = "bincode")]
247                if format == StorageFormat::Bincode {
248                    let bytes = format.serialize(name, resource)?;
249                    SessionStorage::set::<&[u8]>(key, &bytes)?;
250                    return Ok(());
251                }
252
253                let bytes = format.serialize(name, resource)?;
254
255                // unwrapping is okay in this case because
256                // remaining storage formats all return a string
257                // and that string is converted to bytes
258                let string = std::str::from_utf8(&bytes).unwrap();
259                SessionStorage::set::<&str>(key, string)?;
260            },
261        }
262        Ok(())
263    }
264}
265
266impl Display for Storage {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        match self {
269            #[cfg(not(target_family = "wasm"))]
270            Storage::Filesystem { path } => {
271                if let Some(path) = path.to_str() {
272                    write!(f, "{path}")
273                } else {
274                    write!(f, "{path:?}")
275                }
276            },
277            #[cfg(target_family = "wasm")]
278            Storage::LocalStorage { key } => {
279                let separator = std::path::MAIN_SEPARATOR;
280                write!(f, "{separator}local{separator}{key}")
281            },
282            #[cfg(target_family = "wasm")]
283            Storage::SessionStorage { key } => {
284                let separator = std::path::MAIN_SEPARATOR;
285                write!(f, "{separator}session{separator}{key}")
286            },
287        }
288    }
289}