dioxus_storage/
client_storage.rs1#![allow(unused)]
2use dioxus::prelude::*;
3use once_cell::sync::OnceCell;
4use serde::de::DeserializeOwned;
5use serde::{Deserialize, Serialize};
6use std::cell::{Ref, RefMut};
7use std::fmt::Debug;
8use std::io::Write;
9use std::thread::LocalKey;
10use std::{
11 fmt::Display,
12 ops::{Deref, DerefMut},
13};
14use web_sys::{window, Storage};
15
16use crate::storage::{
17 serde_from_string, serde_to_string, storage_entry, try_serde_from_string,
18 use_synced_storage_entry, StorageBacking, StorageEntry, StorageEntryMut,
19};
20
21#[allow(clippy::needless_doctest_main)]
22#[cfg(not(target_arch = "wasm32"))]
33#[macro_export]
34macro_rules! set_dir {
35 () => {
36 $crate::set_dir_name(env!("CARGO_PKG_NAME"));
37 };
38 ($path: literal) => {
39 $crate::set_dir(std::path::PathBuf::from($path));
40 };
41}
42
43#[cfg(not(target_arch = "wasm32"))]
44#[doc(hidden)]
45pub fn set_directory(path: std::path::PathBuf) {
47 LOCATION.set(path).unwrap();
48}
49
50#[cfg(not(target_arch = "wasm32"))]
51#[doc(hidden)]
52pub fn set_dir_name(name: &str) {
53 {
54 set_directory(
55 directories::BaseDirs::new()
56 .unwrap()
57 .data_local_dir()
58 .join(name),
59 )
60 }
61}
62
63#[cfg(not(target_arch = "wasm32"))]
64static LOCATION: OnceCell<std::path::PathBuf> = OnceCell::new();
65
66#[cfg(target_arch = "wasm32")]
67fn local_storage() -> Option<Storage> {
68 window()?.local_storage().ok()?
69}
70
71fn set<T: Serialize>(key: String, value: &T) {
72 #[cfg(not(feature = "ssr"))]
73 {
74 let as_str = serde_to_string(value);
75 #[cfg(target_arch = "wasm32")]
76 {
77 local_storage().unwrap().set_item(&key, &as_str).unwrap();
78 }
79 #[cfg(not(target_arch = "wasm32"))]
80 {
81 let path = LOCATION
82 .get()
83 .expect("Call the set_dir macro before accessing persistant data");
84 std::fs::create_dir_all(path).unwrap();
85 let file_path = path.join(key);
86 let mut file = std::fs::File::create(file_path).unwrap();
87 file.write_all(as_str.as_bytes()).unwrap();
88 }
89 }
90}
91
92fn get<T: DeserializeOwned>(key: &str) -> Option<T> {
93 #[cfg(not(feature = "ssr"))]
94 {
95 #[cfg(target_arch = "wasm32")]
96 {
97 let s: String = local_storage()?.get_item(key).ok()??;
98 try_serde_from_string(&s)
99 }
100 #[cfg(not(target_arch = "wasm32"))]
101 {
102 let path = LOCATION
103 .get()
104 .expect("Call the set_dir macro before accessing persistant data")
105 .join(key);
106 let s = std::fs::read_to_string(path).ok()?;
107 try_serde_from_string(&s)
108 }
109 }
110 #[cfg(feature = "ssr")]
111 None
112}
113
114pub struct ClientStorage;
115
116impl StorageBacking for ClientStorage {
117 type Key = String;
118
119 fn set<T: Serialize>(key: String, value: &T) {
120 set(key, value);
121 }
122
123 fn get<T: DeserializeOwned>(key: &String) -> Option<T> {
124 get(key)
125 }
126}
127
128#[allow(clippy::needless_return)]
132pub fn use_persistent<T: Serialize + DeserializeOwned + Default + 'static>(
133 cx: &ScopeState,
134 key: impl ToString,
135 init: impl FnOnce() -> T,
136) -> &UsePersistent<T> {
137 let mut init = Some(init);
138 #[cfg(feature = "ssr")]
139 let state = use_ref(cx, || {
140 StorageEntry::<ClientStorage, T>::new(key.to_string(), init.take().unwrap()())
141 });
142 #[cfg(all(not(feature = "ssr"), not(feature = "hydrate")))]
144 let state = use_ref(cx, || {
145 StorageEntry::new(
146 key.to_string(),
147 storage_entry::<ClientStorage, T>(key.to_string(), init.take().unwrap()),
148 )
149 });
150 #[cfg(all(not(feature = "ssr"), feature = "hydrate"))]
152 let state = {
153 let state = use_ref(cx, || {
154 StorageEntry::<ClientStorage, T>::new(key.to_string(), init.take().unwrap()())
155 });
156 if cx.generation() == 0 {
157 cx.needs_update();
158 }
159 if cx.generation() == 1 {
160 state.set(StorageEntry::new(
161 key.to_string(),
162 storage_entry::<ClientStorage, T>(key.to_string(), init.take().unwrap()),
163 ));
164 }
165
166 state
167 };
168 cx.use_hook(|| UsePersistent {
169 inner: state.clone(),
170 })
171}
172
173pub struct StorageRef<'a, T: Serialize + DeserializeOwned + Default + 'static> {
174 inner: Ref<'a, StorageEntry<ClientStorage, T>>,
175}
176
177impl<'a, T: Serialize + DeserializeOwned + Default + 'static> Deref for StorageRef<'a, T> {
178 type Target = T;
179
180 fn deref(&self) -> &Self::Target {
181 &self.inner
182 }
183}
184
185pub struct StorageRefMut<'a, T: Serialize + DeserializeOwned + 'static> {
186 inner: RefMut<'a, StorageEntry<ClientStorage, T>>,
187}
188
189impl<'a, T: Serialize + DeserializeOwned + 'static> Deref for StorageRefMut<'a, T> {
190 type Target = T;
191
192 fn deref(&self) -> &Self::Target {
193 &self.inner
194 }
195}
196
197impl<'a, T: Serialize + DeserializeOwned + 'static> DerefMut for StorageRefMut<'a, T> {
198 fn deref_mut(&mut self) -> &mut Self::Target {
199 &mut self.inner.data
200 }
201}
202
203impl<'a, T: Serialize + DeserializeOwned + 'static> Drop for StorageRefMut<'a, T> {
204 fn drop(&mut self) {
205 self.inner.deref_mut().save();
206 }
207}
208
209pub struct UsePersistent<T: Serialize + DeserializeOwned + Default + 'static> {
211 inner: UseRef<StorageEntry<ClientStorage, T>>,
212}
213
214impl<T: Serialize + DeserializeOwned + Default + 'static> UsePersistent<T> {
215 pub fn read(&self) -> StorageRef<T> {
217 StorageRef {
218 inner: self.inner.read(),
219 }
220 }
221
222 pub fn write(&self) -> StorageRefMut<T> {
224 StorageRefMut {
225 inner: self.inner.write(),
226 }
227 }
228
229 pub fn set(&self, value: T) {
231 *self.write() = value;
232 }
233
234 pub fn modify<F: FnOnce(&mut T)>(&self, f: F) {
236 f(&mut self.write());
237 }
238}
239
240impl<T: Serialize + DeserializeOwned + Default + Clone + 'static> UsePersistent<T> {
241 pub fn get(&self) -> T {
243 self.read().clone()
244 }
245}
246
247impl<T: Serialize + DeserializeOwned + Default + 'static> Deref for UsePersistent<T> {
248 type Target = UseRef<StorageEntry<ClientStorage, T>>;
249
250 fn deref(&self) -> &Self::Target {
251 &self.inner
252 }
253}
254
255impl<T: Serialize + DeserializeOwned + Default + 'static> DerefMut for UsePersistent<T> {
256 fn deref_mut(&mut self) -> &mut Self::Target {
257 &mut self.inner
258 }
259}