use std::io;
use anyhow::{Context as _, Result};
use redb::{Database, StorageBackend};
use web_sys::wasm_bindgen::JsValue;
use web_sys::{FileSystemReadWriteOptions, FileSystemSyncAccessHandle};
use crate::MemAndRedb;
#[derive(Debug)]
struct WasmBackend {
sync_handle: FileSystemSyncAccessHandle,
}
fn js_error_to_anyhow(unknown_error: impl Into<JsValue>) -> anyhow::Error {
match gloo_utils::errors::JsError::try_from(unknown_error.into()) {
Ok(error) => error.into(),
Err(error) => anyhow::format_err!(error.to_string()),
}
}
fn js_error_to_io_error(err: impl Into<JsValue>) -> std::io::Error {
std::io::Error::other(js_error_to_anyhow(err))
}
impl WasmBackend {
fn new(sync_handle: FileSystemSyncAccessHandle) -> Self {
Self { sync_handle }
}
fn read_impl(&self, offset: u64, out: &mut [u8]) -> io::Result<()> {
let mut bytes_read = 0;
let options = FileSystemReadWriteOptions::new();
while bytes_read != out.len() {
assert!(bytes_read < out.len());
options.set_at((offset + bytes_read as u64) as f64);
bytes_read += self
.sync_handle
.read_with_u8_array_and_options(&mut out[bytes_read..], &options)
.map_err(js_error_to_io_error)? as usize;
}
Ok(())
}
fn write_impl(&self, offset: u64, data: &[u8]) -> io::Result<()> {
let options = FileSystemReadWriteOptions::new();
let mut bytes_written = 0;
while bytes_written != data.len() {
assert!(bytes_written < data.len());
options.set_at((offset + bytes_written as u64) as f64);
bytes_written += self
.sync_handle
.write_with_u8_array_and_options(&data[bytes_written..], &options)
.map_err(js_error_to_io_error)? as usize;
}
Ok(())
}
fn len_impl(&self) -> io::Result<u64> {
let size = self.sync_handle.get_size().map_err(js_error_to_io_error)?;
Ok(size as u64)
}
fn set_len_impl(&self, len: u64) -> io::Result<()> {
self.sync_handle
.truncate_with_f64(len as f64)
.map_err(js_error_to_io_error)?;
Ok(())
}
fn sync_data_impl(&self) -> io::Result<()> {
self.sync_handle.flush().map_err(js_error_to_io_error)?;
Ok(())
}
}
impl StorageBackend for WasmBackend {
fn len(&self) -> io::Result<u64> {
self.len_impl()
}
fn read(&self, offset: u64, out: &mut [u8]) -> io::Result<()> {
self.read_impl(offset, out)
}
fn set_len(&self, len: u64) -> io::Result<()> {
self.set_len_impl(len)
}
fn sync_data(&self) -> io::Result<()> {
self.sync_data_impl()
}
fn write(&self, offset: u64, data: &[u8]) -> io::Result<()> {
self.write_impl(offset, data)
}
}
impl redb2::StorageBackend for WasmBackend {
fn len(&self) -> io::Result<u64> {
self.len_impl()
}
fn read(&self, offset: u64, len: usize) -> io::Result<Vec<u8>> {
let mut buf = vec![0u8; len];
self.read_impl(offset, &mut buf)?;
Ok(buf)
}
fn set_len(&self, len: u64) -> io::Result<()> {
self.set_len_impl(len)
}
fn sync_data(&self, _eventual: bool) -> io::Result<()> {
self.sync_data_impl()
}
fn write(&self, offset: u64, data: &[u8]) -> io::Result<()> {
self.write_impl(offset, data)
}
}
unsafe impl Send for WasmBackend {}
unsafe impl Sync for WasmBackend {}
impl MemAndRedb {
pub fn new(file: FileSystemSyncAccessHandle) -> Result<Self> {
let backend = WasmBackend::new(file.clone());
match Database::builder().create_with_backend(backend) {
Ok(db) => Ok(Self::new_from_redb(db)?),
Err(redb::DatabaseError::UpgradeRequired(_)) => {
Self::migrate_v2_to_v3(&file)?;
let backend = WasmBackend::new(file);
let db = Database::builder()
.create_with_backend(backend)
.context("Failed to open redb database after v2->v3 migration")?;
Ok(Self::new_from_redb(db)?)
}
Err(e) => Err(anyhow::Error::from(e).context("Failed to create/open redb database")),
}
}
fn migrate_v2_to_v3(file: &FileSystemSyncAccessHandle) -> Result<()> {
tracing::info!("Migrating redb database from v2 to v3 file format");
let backend = WasmBackend::new(file.clone());
let mut db = redb2::Database::builder()
.create_with_backend(backend)
.context("Failed to open redb v2 database for migration")?;
db.upgrade()
.context("Failed to upgrade redb database to v3 format")?;
tracing::info!("Successfully migrated redb database to v3 format");
Ok(())
}
}