batbox_preferences/
lib.rs

1//! Saving/loading preferences (small amounts of user data)
2//!
3//! Web implementation uses [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)
4#![warn(missing_docs)]
5
6use serde::{de::DeserializeOwned, Serialize};
7
8/// Base path where preferences are going to be saved/loaded from
9pub fn base_path() -> std::path::PathBuf {
10    #[cfg(target_arch = "wasm32")]
11    {
12        ".".into() // TODO: detect app name by url?
13    }
14    #[cfg(not(target_arch = "wasm32"))]
15    {
16        let exe = std::env::current_exe().expect("Failed to find current exe");
17        let app_name = exe.file_stem().unwrap();
18        if let Some(dirs) =
19            directories::ProjectDirs::from("", "", app_name.to_str().expect("Exe name is invalid"))
20        {
21            return dirs.preference_dir().to_path_buf();
22        }
23        if let Some(dir) = exe.parent() {
24            return dir.to_path_buf();
25        }
26        std::env::current_dir().unwrap()
27    }
28}
29
30/// Save given value for given key
31pub fn save<T: Serialize>(key: &str, value: &T) {
32    let base_path = base_path();
33    let path = base_path.join(key);
34    #[cfg(target_arch = "wasm32")]
35    {
36        let path = path.to_str().unwrap();
37        if let Ok(Some(storage)) = web_sys::window().unwrap().local_storage() {
38            if let Err(e) = storage.set_item(
39                path,
40                &serde_json::to_string(value).expect("Failed to serialize"),
41            ) {
42                let _ = e; // TODO: error?
43            }
44        }
45    }
46    #[cfg(not(target_arch = "wasm32"))]
47    {
48        use std::io::Write;
49        if let Err(e) = std::fs::create_dir_all(&base_path) {
50            log::error!("Failed to create preferences base path: {}", e);
51            return;
52        }
53        let path = &path;
54        let mut file = match std::fs::File::create(path) {
55            Ok(file) => file,
56            Err(e) => {
57                log::error!("Failed to create {:?}: {}", path, e);
58                return;
59            }
60        };
61        if let Err(e) = file.write_all(serde_json::to_string_pretty(value).unwrap().as_bytes()) {
62            log::error!("Failed to save {:?}: {}", path, e);
63        }
64    }
65}
66
67/// Load value for given key
68pub fn load<T: DeserializeOwned>(key: &str) -> Option<T> {
69    let base_path = base_path();
70    let path = base_path.join(key);
71    #[cfg(target_arch = "wasm32")]
72    {
73        let path = path.to_str().unwrap();
74        if let Ok(Some(storage)) = web_sys::window().unwrap().local_storage() {
75            match storage
76                .get_item(path)
77                .ok()
78                .flatten()
79                .map(|s| serde_json::from_str(&s))
80            {
81                Some(Ok(value)) => Some(value),
82                Some(Err(e)) => {
83                    log::error!("Failed to deserialize {:?}: {}", path, e);
84                    None
85                }
86                None => None,
87            }
88        } else {
89            None
90        }
91    }
92    #[cfg(not(target_arch = "wasm32"))]
93    {
94        let path = &path;
95        let file = match std::fs::File::open(path) {
96            Ok(file) => file,
97            Err(e) => {
98                log::warn!("Failed to open {:?}: {}", path, e);
99                return None;
100            }
101        };
102        match serde_json::from_reader(file) {
103            Ok(value) => {
104                log::debug!("Successfully loaded {:?}", path);
105                Some(value)
106            }
107            Err(e) => {
108                log::error!("Failed to deserialize {:?}: {}", path, e);
109                None
110            }
111        }
112    }
113}