dioxus_storage/
client_storage.rs

1#![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/// Set the directory where the storage files are located on non-wasm targets.
23///
24/// ```rust
25/// fn main(){
26///     // set the directory to the default location
27///     set_dir!();
28///     // set the directory to a custom location
29///     set_dir!(PathBuf::from("path/to/dir"));
30/// }
31/// ```
32#[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)]
45/// Sets the directory where the storage files are located.
46pub 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/// A persistent storage hook that can be used to store data across application reloads.
129///
130/// Depending on the platform this uses either local storage or a file storage
131#[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    // if hydration is not enabled we can just set the storage
143    #[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    // otherwise render the initial value and then hydrate after the first render
151    #[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
209/// Storage that persists across application reloads
210pub struct UsePersistent<T: Serialize + DeserializeOwned + Default + 'static> {
211    inner: UseRef<StorageEntry<ClientStorage, T>>,
212}
213
214impl<T: Serialize + DeserializeOwned + Default + 'static> UsePersistent<T> {
215    /// Returns a reference to the value
216    pub fn read(&self) -> StorageRef<T> {
217        StorageRef {
218            inner: self.inner.read(),
219        }
220    }
221
222    /// Returns a mutable reference to the value
223    pub fn write(&self) -> StorageRefMut<T> {
224        StorageRefMut {
225            inner: self.inner.write(),
226        }
227    }
228
229    /// Sets the value
230    pub fn set(&self, value: T) {
231        *self.write() = value;
232    }
233
234    /// Modifies the value
235    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    /// Returns a clone of the value
242    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}