use js_sys::{Array, Promise, Uint8Array};
use std::collections::HashMap;
use wasm_bindgen::JsCast;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{IdbDatabase, IdbRequest, IdbTransaction, IdbTransactionMode};
fn request_to_promise(request: &IdbRequest) -> Promise {
let req = request.clone();
Promise::new(&mut |resolve, reject| {
let req_ok = req.clone();
let on_success: Closure<dyn FnMut(web_sys::Event)> =
Closure::once(move |_: web_sys::Event| {
let result = req_ok.result().unwrap_or(JsValue::NULL);
resolve.call1(&JsValue::NULL, &result).ok();
});
let on_error: Closure<dyn FnMut(web_sys::Event)> =
Closure::once(move |_: web_sys::Event| {
reject
.call1(&JsValue::NULL, &JsValue::from_str("IdbRequest failed"))
.ok();
});
req.set_onsuccess(Some(on_success.as_ref().unchecked_ref()));
req.set_onerror(Some(on_error.as_ref().unchecked_ref()));
on_success.forget();
on_error.forget();
})
}
fn transaction_to_promise(tx: &IdbTransaction) -> Promise {
let tx = tx.clone();
Promise::new(&mut |resolve, reject| {
let on_complete: Closure<dyn FnMut(web_sys::Event)> =
Closure::once(move |_: web_sys::Event| {
resolve.call0(&JsValue::NULL).ok();
});
let on_error: Closure<dyn FnMut(web_sys::Event)> =
Closure::once(move |_: web_sys::Event| {
reject
.call1(&JsValue::NULL, &JsValue::from_str("IdbTransaction failed"))
.ok();
});
tx.set_oncomplete(Some(on_complete.as_ref().unchecked_ref()));
tx.set_onerror(Some(on_error.as_ref().unchecked_ref()));
on_complete.forget();
on_error.forget();
})
}
pub struct IndexedDbBackend {
pub(crate) db: IdbDatabase,
pub(crate) store_name: String,
}
impl IndexedDbBackend {
pub async fn open(db_name: &str) -> Result<Self, JsValue> {
let window = web_sys::window().ok_or_else(|| JsValue::from_str("no window object"))?;
let idb_factory = window
.indexed_db()?
.ok_or_else(|| JsValue::from_str("IndexedDB not available"))?;
let store_name = db_name.to_string();
let store_name_upgrade = store_name.clone();
let open_request = idb_factory.open_with_u32(db_name, 1)?;
let on_upgrade: Closure<dyn FnMut(web_sys::Event)> =
Closure::once(move |event: web_sys::Event| {
let target = event.target().unwrap();
let request: web_sys::IdbOpenDbRequest = target.dyn_into().unwrap();
let db: IdbDatabase = request.result().unwrap().dyn_into().unwrap();
if !db.object_store_names().contains(&store_name_upgrade) {
db.create_object_store(&store_name_upgrade).unwrap();
}
});
open_request.set_onupgradeneeded(Some(on_upgrade.as_ref().unchecked_ref()));
on_upgrade.forget();
JsFuture::from(request_to_promise(open_request.as_ref())).await?;
let db: IdbDatabase = open_request.result()?.dyn_into()?;
Ok(Self { db, store_name })
}
pub async fn load_all_pages(&self) -> Result<HashMap<u64, Vec<u8>>, JsValue> {
let tx = self
.db
.transaction_with_str_and_mode(&self.store_name, IdbTransactionMode::Readonly)?;
let store = tx.object_store(&self.store_name)?;
let keys_req = store.get_all_keys()?;
let keys_val = JsFuture::from(request_to_promise(keys_req.as_ref())).await?;
let keys_arr: Array = keys_val.dyn_into()?;
let vals_req = store.get_all()?;
let vals_val = JsFuture::from(request_to_promise(vals_req.as_ref())).await?;
let vals_arr: Array = vals_val.dyn_into()?;
let mut pages = HashMap::with_capacity(keys_arr.length() as usize);
for i in 0..keys_arr.length() {
let key = keys_arr.get(i);
let page_id = key
.as_f64()
.ok_or_else(|| JsValue::from_str("page_id is not a number"))?
as u64;
let val = vals_arr.get(i);
let arr: Uint8Array = val.dyn_into()?;
pages.insert(page_id, arr.to_vec());
}
Ok(pages)
}
pub fn clone_handle(&self) -> Self {
Self {
db: self.db.clone(),
store_name: self.store_name.clone(),
}
}
pub async fn write_pages(&self, pages: Vec<(u64, Vec<u8>)>) -> Result<(), JsValue> {
if pages.is_empty() {
return Ok(());
}
let tx = self
.db
.transaction_with_str_and_mode(&self.store_name, IdbTransactionMode::Readwrite)?;
let store = tx.object_store(&self.store_name)?;
for (page_id, data) in &pages {
let key = JsValue::from_f64(*page_id as f64);
let arr = Uint8Array::from(data.as_slice());
store.put_with_key(&arr, &key)?;
}
JsFuture::from(transaction_to_promise(&tx)).await?;
Ok(())
}
}