dioxus_sdk_storage/client_storage/
fs.rs

1use crate::{StorageChannelPayload, StorageSubscription};
2use dioxus::logger::tracing::trace;
3use serde::Serialize;
4use serde::de::DeserializeOwned;
5use std::collections::HashMap;
6use std::io::Write;
7use std::sync::{OnceLock, RwLock};
8use tokio::sync::watch::{Receiver, channel};
9
10use crate::{StorageBacking, StorageSubscriber, serde_to_string, try_serde_from_string};
11
12#[doc(hidden)]
13/// Sets the directory where the storage files are located.
14pub fn set_directory(path: std::path::PathBuf) {
15    LOCATION.set(path).unwrap();
16}
17
18#[doc(hidden)]
19pub fn set_dir_name(name: &str) {
20    set_directory(
21        directories::BaseDirs::new()
22            .unwrap()
23            .data_local_dir()
24            .join(name),
25    )
26}
27
28/// The location where the storage files are located.
29static LOCATION: OnceLock<std::path::PathBuf> = OnceLock::new();
30
31/// Set a value in the configured storage location using the key as the file name.
32fn set<T: Serialize>(key: String, value: &T) {
33    let as_str = serde_to_string(value);
34    let path = LOCATION
35        .get()
36        .expect("Call the set_dir macro before accessing persistant data");
37    std::fs::create_dir_all(path).unwrap();
38    let file_path = path.join(key);
39    let mut file = std::fs::File::create(file_path).unwrap();
40    file.write_all(as_str.as_bytes()).unwrap();
41}
42
43/// Get a value from the configured storage location using the key as the file name.
44fn get<T: DeserializeOwned>(key: &str) -> Option<T> {
45    let path = LOCATION
46        .get()
47        .expect("Call the set_dir macro before accessing persistant data")
48        .join(key);
49    let s = std::fs::read_to_string(path).ok()?;
50    try_serde_from_string(&s)
51}
52
53#[derive(Clone)]
54pub struct LocalStorage;
55
56impl StorageBacking for LocalStorage {
57    type Key = String;
58
59    fn set<T: Serialize + Send + Sync + Clone + 'static>(key: String, value: &T) {
60        let key_clone = key.clone();
61        let value_clone = (*value).clone();
62        set(key, value);
63
64        // If the subscriptions map is not initialized, we don't need to notify any subscribers.
65        if let Some(subscriptions) = SUBSCRIPTIONS.get() {
66            let read_binding = subscriptions.read().unwrap();
67            if let Some(subscription) = read_binding.get(&key_clone) {
68                subscription
69                    .tx
70                    .send(StorageChannelPayload::new(value_clone))
71                    .unwrap();
72            }
73        }
74    }
75
76    fn get<T: DeserializeOwned>(key: &String) -> Option<T> {
77        get(key)
78    }
79}
80
81// Note that this module contains an optimization that differs from the web version. Dioxus Desktop runs all windows in
82// the same thread, meaning that we can just directly notify the subscribers via the same channels, rather than using the
83// storage event listener.
84impl StorageSubscriber<LocalStorage> for LocalStorage {
85    fn subscribe<T: DeserializeOwned + Send + Sync + Clone + 'static>(
86        key: &<LocalStorage as StorageBacking>::Key,
87    ) -> Receiver<StorageChannelPayload> {
88        // Initialize the subscriptions map if it hasn't been initialized yet.
89        let subscriptions = SUBSCRIPTIONS.get_or_init(|| RwLock::new(HashMap::new()));
90
91        // Check if the subscription already exists. If it does, return the existing subscription's channel.
92        // If it doesn't, create a new subscription and return its channel.
93        let read_binding = subscriptions.read().unwrap();
94        match read_binding.get(key) {
95            Some(subscription) => subscription.tx.subscribe(),
96            None => {
97                drop(read_binding);
98                let (tx, rx) = channel::<StorageChannelPayload>(StorageChannelPayload::default());
99                let subscription = StorageSubscription::new::<LocalStorage, T>(tx, key.clone());
100
101                subscriptions
102                    .write()
103                    .unwrap()
104                    .insert(key.clone(), subscription);
105                rx
106            }
107        }
108    }
109
110    fn unsubscribe(key: &<LocalStorage as StorageBacking>::Key) {
111        trace!("Unsubscribing from \"{}\"", key);
112
113        // Fail silently if unsubscribe is called but the subscriptions map isn't initialized yet.
114        if let Some(subscriptions) = SUBSCRIPTIONS.get() {
115            let read_binding = subscriptions.read().unwrap();
116
117            // If the subscription exists, remove it from the subscriptions map.
118            if read_binding.contains_key(key) {
119                trace!("Found entry for \"{}\"", key);
120                drop(read_binding);
121                subscriptions.write().unwrap().remove(key);
122            }
123        }
124    }
125}
126
127/// A map of all the channels that are currently subscribed to and the getters for the corresponding storage entry.
128/// This gets initialized lazily.
129static SUBSCRIPTIONS: OnceLock<RwLock<HashMap<String, StorageSubscription>>> = OnceLock::new();