Skip to main content

workflow_store/
store.rs

1use crate::result::Result;
2use cfg_if::cfg_if;
3use std::collections::hash_map::DefaultHasher;
4use std::hash::{Hash, Hasher};
5
6cfg_if! {
7    if #[cfg(not(target_arch = "wasm32"))] {
8        use std::path::PathBuf;
9        use tokio::fs;
10    } else {
11        // use base64::{Engine as _, engine::general_purpose};
12    }
13}
14
15///
16/// # Store
17///
18/// A simple file loader that allows user to
19/// specify different paths on various
20/// operating systems with fallbacks.
21///
22pub struct Store {
23    /// Path used on Linux (falls back to `unix`, then `generic`).
24    pub linux: Option<String>,
25    /// Path used on macOS (falls back to `unix`, then `generic`).
26    pub macos: Option<String>,
27    /// Path used on Unix targets (falls back to `generic`).
28    pub unix: Option<String>,
29    /// Path used on Windows (falls back to `generic`).
30    pub windows: Option<String>,
31    /// Fallback path for all operating systems.
32    pub generic: Option<String>,
33    /// Browser local-storage key (falls back to a hex hash of `generic`).
34    pub browser: Option<String>,
35}
36
37impl Default for Store {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl Store {
44    /// Creates a new [`Store`] with all paths unset.
45    pub fn new() -> Store {
46        Store {
47            linux: None,
48            macos: None,
49            unix: None,
50            windows: None,
51            generic: None,
52            browser: None,
53        }
54    }
55
56    /// Sets the path used on Linux targets.
57    pub fn with_linux(&mut self, linux: &str) -> &mut Store {
58        self.linux = Some(linux.to_string());
59        self
60    }
61
62    /// Sets the path used on macOS targets.
63    pub fn with_macos(&mut self, macos: &str) -> &mut Store {
64        self.macos = Some(macos.to_string());
65        self
66    }
67
68    /// Sets the Unix path, used as a fallback for Linux and macOS targets.
69    pub fn with_unix(&mut self, unix: &str) -> &mut Store {
70        self.unix = Some(unix.to_string());
71        self
72    }
73
74    /// Sets the path used on Windows targets.
75    pub fn with_windows(&mut self, windows: &str) -> &mut Store {
76        self.windows = Some(windows.to_string());
77        self
78    }
79
80    /// Sets the generic fallback path used when no OS-specific path matches.
81    pub fn with_generic(&mut self, generic: &str) -> &mut Store {
82        self.generic = Some(generic.to_string());
83        self
84    }
85
86    /// Sets the browser local-storage key used under the `wasm32` target.
87    pub fn with_browser(&mut self, browser: &str) -> &mut Store {
88        self.browser = Some(browser.to_string());
89        self
90    }
91
92    /// Resolves the storage path for the current operating environment,
93    /// applying the OS-specific fallback chain. Panics if no suitable
94    /// path has been configured.
95    pub fn filename(&self) -> String {
96        cfg_if! {
97            if #[cfg(target_os = "macos")] {
98                find(&[self.macos.as_ref(),self.unix.as_ref(),self.generic.as_ref()])
99            } else if #[cfg(target_os = "linux")] {
100                find(&[self.linux.as_ref(),self.unix.as_ref(),self.generic.as_ref()])
101            } else if #[cfg(target_family = "unix")] {
102                find(&[self.unix.as_ref(),self.generic.as_ref()])
103            } else if #[cfg(target_family = "windows")] {
104                find(&[self.windows.as_ref(),self.generic.as_ref()])
105            } else if #[cfg(target_arch = "wasm32")] {
106                if let Some(browser) = self.browser.as_ref() {
107                    browser.clone()
108                } else if let Some(generic) = self.generic.as_ref() {
109                    // hash of generic
110                    hash(generic)
111                } else {
112                    panic!("no path found for the current operating environment");
113                }
114            }
115        }
116    }
117
118    cfg_if! {
119        if #[cfg(target_arch = "wasm32")] {
120            pub async fn exists(&self) -> Result<bool> {
121                let filename = self.filename();
122                Ok(local_storage().get_item(&filename)?.is_some())
123            }
124
125            pub async fn read_to_string(&self) -> Result<String> {
126                let filename = self.filename();
127                let v = local_storage().get_item(&filename)?.unwrap();
128                // Ok(general_purpose::STANDARD.decode(v)?)
129                Ok(v)
130            }
131
132            pub async fn write_string(&self, data: &str) -> Result<()> {
133                let filename = self.filename();
134                // let v = general_purpose::STANDARD.encode(data);
135                local_storage().set_item(&filename, data)?;
136                Ok(())
137            }
138
139        } else {
140            /// Returns `true` if a file exists at the resolved file path.
141            pub async fn exists(&self) -> Result<bool> {
142                let filename = parse(self.filename());
143                Ok(fs::metadata(&filename).await.is_ok())
144            }
145
146            /// Reads the entire contents of the resolved file path into a string.
147            pub async fn read_to_string(&self) -> Result<String> {
148                let filename = parse(self.filename());
149                Ok(fs::read_to_string(&filename).await?)
150            }
151
152            /// Writes the given string to the resolved file path,
153            /// overwriting any existing contents.
154            pub async fn write_string(&self, data: &str) -> Result<()> {
155                let filename = parse(self.filename());
156                Ok(fs::write(&filename, data).await?)
157            }
158        }
159    }
160}
161
162cfg_if! {
163    if #[cfg(not(target_arch = "wasm32"))] {
164        /// Converts a path string into a [`PathBuf`], expanding a leading
165        /// `~` to the user's home directory.
166        pub fn parse(path : String) -> PathBuf {
167
168            if let Some(stripped) = path.strip_prefix('~') {
169                let home_dir: PathBuf = home::home_dir().unwrap();
170                home_dir.join(stripped)
171            } else {
172                PathBuf::from(path)
173            }
174        }
175    } else {
176        pub fn local_storage() -> web_sys::Storage {
177            web_sys::window().unwrap().local_storage().unwrap().unwrap()
178        }
179    }
180}
181
182/// Returns the first `Some` path from the given list of candidates,
183/// panicking if every candidate is `None`.
184pub fn find(paths: &[Option<&String>]) -> String {
185    for path in paths.iter() {
186        if let Some(path) = *path {
187            return path.clone();
188        }
189    }
190    panic!("no path found for the current operating environment");
191}
192
193/// Computes a stable hexadecimal hash string for any [`Hash`]-able value,
194/// used to derive a browser local-storage key when no explicit key is given.
195pub fn hash<T>(t: T) -> String
196where
197    T: Hash,
198{
199    let mut hasher = DefaultHasher::new();
200    t.hash(&mut hasher);
201    let v = hasher.finish();
202    format!("{v:x}")
203}