#![cfg(target_arch = "wasm32")]
use super::StorageBackend;
use crate::engine::types::{DbError, LogEntry};
use std::sync::Mutex;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
pub struct OpfsStorage {
handle: Mutex<web_sys::FileSystemSyncAccessHandle>,
}
impl OpfsStorage {
pub async fn new(db_name: &str) -> Result<Self, DbError> {
let global = js_sys::global()
.dyn_into::<web_sys::WorkerGlobalScope>()
.map_err(|_| DbError::WriteError)?;
let navigator = global.navigator();
let storage = navigator.storage();
let root_val: JsValue = JsFuture::from(storage.get_directory())
.await
.map_err(|_| DbError::WriteError)?;
let root_dir: web_sys::FileSystemDirectoryHandle = root_val.unchecked_into();
let mut opts = web_sys::FileSystemGetFileOptions::new();
opts.set_create(true);
let file_val: JsValue = JsFuture::from(
root_dir.get_file_handle_with_options(db_name, &opts)
)
.await
.map_err(|_| DbError::WriteError)?;
let file_handle: web_sys::FileSystemFileHandle = file_val.unchecked_into();
let sync_val: JsValue = JsFuture::from(file_handle.create_sync_access_handle())
.await
.map_err(|_| DbError::WriteError)?;
let sync_handle: web_sys::FileSystemSyncAccessHandle = sync_val.unchecked_into();
Ok(Self { handle: Mutex::new(sync_handle) })
}
}
impl Drop for OpfsStorage {
fn drop(&mut self) {
if let Ok(handle) = self.handle.lock() {
let _ = handle.close();
}
}
}
impl StorageBackend for OpfsStorage {
fn write_entry(&self, entry: &LogEntry) -> Result<(), DbError> {
let mut json_line = serde_json::to_string(entry)?;
json_line.push('\n');
let handle = self.handle.lock().unwrap();
let size = handle.get_size().map_err(|_| DbError::WriteError)? as f64;
let mut opts = web_sys::FileSystemReadWriteOptions::new();
opts.set_at(size);
let mut bytes = json_line.into_bytes();
handle
.write_with_u8_array_and_options(&mut bytes, &opts)
.map_err(|_| DbError::WriteError)?;
handle.flush().map_err(|_| DbError::WriteError)?;
Ok(())
}
fn read_at(&self, offset: u64, length: u32) -> Result<Vec<u8>, DbError> {
let handle = self.handle.lock().unwrap();
let mut buf = vec![0u8; length as usize];
let mut opts = web_sys::FileSystemReadWriteOptions::new();
opts.set_at(offset as f64);
handle
.read_with_u8_array_and_options(&mut buf, &opts)
.map_err(|_| DbError::WriteError)?;
Ok(buf)
}
fn read_log(&self) -> Result<Vec<LogEntry>, DbError> {
let handle = self.handle.lock().unwrap();
let size = handle.get_size().map_err(|_| DbError::WriteError)? as usize;
if size == 0 { return Ok(Vec::new()); }
let mut buf = vec![0u8; size];
let mut opts = web_sys::FileSystemReadWriteOptions::new();
opts.set_at(0.0);
handle
.read_with_u8_array_and_options(&mut buf, &opts)
.map_err(|_| DbError::WriteError)?;
let data_str = String::from_utf8_lossy(&buf);
Ok(data_str.lines()
.filter_map(|line| serde_json::from_str::<LogEntry>(line).ok())
.collect())
}
fn get_size(&self) -> Result<u64, DbError> {
let handle = self.handle.lock().unwrap();
let size = handle.get_size().map_err(|_| DbError::WriteError)? as u64;
Ok(size)
}
fn compact(&self, entries: Vec<LogEntry>) -> Result<(), DbError> {
let handle = self.handle.lock().unwrap();
handle.truncate_with_f64(0.0).map_err(|_| DbError::WriteError)?;
let mut all_data = String::new();
for entry in entries {
all_data.push_str(&serde_json::to_string(&entry)?);
all_data.push('\n');
}
let mut bytes = all_data.into_bytes();
let mut opts = web_sys::FileSystemReadWriteOptions::new();
opts.set_at(0.0);
handle
.write_with_u8_array_and_options(&mut bytes, &opts)
.map_err(|_| DbError::WriteError)?;
handle.flush().map_err(|_| DbError::WriteError)?;
Ok(())
}
}