use std::collections::HashMap;
use serde_json::Value;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{
FileSystemCreateWritableOptions, FileSystemGetFileOptions, FileSystemWritableFileStream,
};
use crate::opfs_helpers::{opfs_read_all, opfs_root, OpfsError};
use super::SettingsError;
impl From<OpfsError> for SettingsError {
fn from(e: OpfsError) -> Self {
SettingsError::Opfs(e.to_string())
}
}
#[derive(Debug)]
pub struct SettingsStore {
namespace: String,
map: HashMap<String, Value>,
}
impl SettingsStore {
pub async fn open(namespace: &str) -> Result<Self, SettingsError> {
let root = opfs_root().await?;
let filename = format!("{namespace}.json");
let map = match opfs_read_all(&root, &filename).await {
Ok(bytes) => {
let text =
String::from_utf8(bytes).map_err(|e| SettingsError::Serde(e.to_string()))?;
serde_json::from_str::<HashMap<String, Value>>(&text)
.map_err(|e| SettingsError::Serde(e.to_string()))?
}
Err(OpfsError::FileNotFound) => HashMap::new(),
Err(e) => return Err(SettingsError::from(e)),
};
Ok(Self {
namespace: namespace.to_string(),
map,
})
}
pub fn get<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
let value = self.map.get(key)?;
serde_json::from_value(value.clone()).ok()
}
pub fn set<T: serde::Serialize>(
&mut self,
key: &str,
value: &T,
) -> Result<(), SettingsError> {
let json_value =
serde_json::to_value(value).map_err(|e| SettingsError::Serde(e.to_string()))?;
self.map.insert(key.to_string(), json_value);
Ok(())
}
pub fn remove(&mut self, key: &str) -> bool {
self.map.remove(key).is_some()
}
pub fn keys(&self) -> Vec<String> {
self.map.keys().cloned().collect()
}
pub fn contains(&self, key: &str) -> bool {
self.map.contains_key(key)
}
pub async fn save(&self) -> Result<(), SettingsError> {
let json = serde_json::to_string_pretty(&self.map)
.map_err(|e| SettingsError::Serde(e.to_string()))?;
let root = opfs_root().await?;
let filename = format!("{}.json", self.namespace);
let fopts = FileSystemGetFileOptions::new();
fopts.set_create(true);
let fh: web_sys::FileSystemFileHandle =
JsFuture::from(root.get_file_handle_with_options(&filename, &fopts))
.await
.map_err(|e| SettingsError::Opfs(format!("{e:?}")))?
.dyn_into()
.map_err(|e| SettingsError::Opfs(format!("{e:?}")))?;
let write_opts = FileSystemCreateWritableOptions::new();
write_opts.set_keep_existing_data(false);
let writable: FileSystemWritableFileStream =
JsFuture::from(fh.create_writable_with_options(&write_opts))
.await
.map_err(|e| SettingsError::Opfs(format!("{e:?}")))?
.dyn_into()
.map_err(|e| SettingsError::Opfs(format!("{e:?}")))?;
let bytes = json.into_bytes();
let arr = js_sys::Uint8Array::from(bytes.as_slice());
JsFuture::from(
writable
.write_with_buffer_source(&arr)
.map_err(|e| SettingsError::Opfs(format!("{e:?}")))?,
)
.await
.map_err(|e| SettingsError::Opfs(format!("{e:?}")))?;
JsFuture::from(writable.close())
.await
.map_err(|e| SettingsError::Opfs(format!("{e:?}")))?;
Ok(())
}
}