From 09d499d5a3ff081c010e11996994da48e1086687 Mon Sep 17 00:00:00 2001 From: Spxg Date: Thu, 29 May 2025 22:44:39 +0800 Subject: [PATCH] Add SQLite3MultipleCiphers support --- .github/workflows/pages.yaml | 6 ++ Cargo.lock | 30 ++++++++- Cargo.toml | 9 ++- index.html | 5 +- src/app/advanced_options_menu.rs | 26 +++++++- src/app/config_element.rs | 5 +- src/app/header.rs | 27 ++++---- src/app/mod.rs | 2 +- src/app/playground.rs | 63 +++++++------------ src/app/state.rs | 7 +-- src/bin/app.rs | 4 +- src/bin/sqlite3.rs | 3 + src/bin/sqlite3mc.rs | 3 + src/bin/worker.rs | 42 ------------- src/lib.rs | 76 +++++++++++++++++------ src/worker/mod.rs | 103 ++++++++++++++++++++++++++----- 16 files changed, 264 insertions(+), 147 deletions(-) create mode 100644 src/bin/sqlite3.rs create mode 100644 src/bin/sqlite3mc.rs delete mode 100644 src/bin/worker.rs diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 3e79b53..910f52a 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -29,6 +29,12 @@ jobs: - name: Build run: | + git clone https://github.com/emscripten-core/emsdk.git + cd emsdk + ./emsdk install latest + ./emsdk activate latest + source ./emsdk_env.sh + cd .. trunk build --release - name: Deploy diff --git a/Cargo.lock b/Cargo.lock index b89795c..a61306f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,6 +170,15 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +[[package]] +name = "cc" +version = "1.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -1773,6 +1782,12 @@ dependencies = [ "syn", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "slab" version = "0.4.9" @@ -1851,9 +1866,9 @@ dependencies = [ [[package]] name = "sqlite-wasm-rs" version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c6fdb636283dad1283b20bc62157ac68a7124fde269723467689c4fd6d356c" +source = "git+https://github.com/Spxg/sqlite-wasm-rs#9912b461e8e551feb400b01f3abd94e4303bef21" dependencies = [ + "cc", "fragile", "indexed_db_futures", "js-sys", @@ -1861,6 +1876,7 @@ dependencies = [ "parking_lot", "thiserror 2.0.12", "tokio", + "wasm-array-cp", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2168,6 +2184,16 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-array-cp" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb633b3e235f0ebe0a35162adc1e0293fc4b7e3f3a6fc7b5374d80464267ff84" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" diff --git a/Cargo.toml b/Cargo.toml index b49cae1..f3ab310 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,14 +18,14 @@ aceditor = { path = "crates/aceditor" } split-grid = { path = "crates/split-grid" } floating-ui = { path = "crates/floating-ui" } -sqlite-wasm-rs = { version = "0.3.6", default-features = false, features = ["precompiled"] } +sqlite-wasm-rs = { git = "https://github.com/Spxg/sqlite-wasm-rs", default-features = false, optional = true } console_error_panic_hook = "0.1.7" leptos = { version = "0.8.2", features = ["csr"] } reactive_stores = "0.2.2" thiserror = "2.0.12" serde = "1.0.219" serde_json = "1.0.140" -web-sys = { version = "0.3.77", features = ["BlobPropertyBag", "Clipboard", "DedicatedWorkerGlobalScope", "File", "FileList", "HtmlSelectElement", "MediaQueryList", "Navigator", "Storage", "Worker", "WorkerOptions", "WorkerType"] } +web-sys = { version = "0.3.77", features = ["BlobPropertyBag", "Clipboard", "DedicatedWorkerGlobalScope", "DomException", "File", "FileList", "HtmlSelectElement", "MediaQueryList", "Navigator", "Storage", "Worker", "WorkerOptions", "WorkerType"] } serde-wasm-bindgen = "0.6.5" parking_lot = "0.12.3" once_cell = "1.21.3" @@ -39,3 +39,8 @@ fragile = "2.0.1" hex = "0.4.3" prettytable-rs = "0.10.0" sqlformat = "0.3.5" + +[features] +default = ["sqlite3"] +sqlite3 = ["sqlite-wasm-rs/precompiled"] +sqlite3mc = ["sqlite-wasm-rs/sqlite3mc"] diff --git a/index.html b/index.html index bdd53c9..bca9ea8 100644 --- a/index.html +++ b/index.html @@ -53,8 +53,9 @@ - - + + + diff --git a/src/app/advanced_options_menu.rs b/src/app/advanced_options_menu.rs index 67d30f9..0ba07b0 100644 --- a/src/app/advanced_options_menu.rs +++ b/src/app/advanced_options_menu.rs @@ -2,7 +2,8 @@ use leptos::prelude::*; use reactive_stores::Store; use crate::app::{ - GlobalState, GlobalStateStoreFields, config_element::Either, menu_group::MenuGroup, + GlobalState, GlobalStateStoreFields, config_element::Either, menu_aside::MenuAside, + menu_group::MenuGroup, }; #[component] @@ -25,6 +26,29 @@ pub fn AdvancedOptionsMenu() -> impl IntoView { state.run_selected_sql().set(*value); } /> + + "Using an encrypted database. + https://github.com/utelle/SQLite3MultipleCiphers + https://utelle.github.io/SQLite3MultipleCiphers" + + } + .into_any(), + ) + id="multiple_ciphers".into() + name="Multiple Ciphers".into() + a=true + b=false + a_label=Some("On".to_string()) + b_label=Some("Off".to_string()) + value=move || *state.multiple_ciphers().read() + is_default=Box::new(move || !*state.multiple_ciphers().read()) + on_change=move |value: &bool| { + state.multiple_ciphers().set(*value); + } + /> } diff --git a/src/app/config_element.rs b/src/app/config_element.rs index ab628fd..2e3450a 100644 --- a/src/app/config_element.rs +++ b/src/app/config_element.rs @@ -13,6 +13,7 @@ istyles!( #[component] pub fn Either( + #[prop(default =None)] aside: Option, on_change: E, id: String, a: T, @@ -58,7 +59,7 @@ where let b2 = Arc::clone(&b1); view! { - +
, #[prop(default = Box::new(|| true))] is_default: Box bool + Send>, children: Children, ) -> impl IntoView { @@ -121,6 +123,7 @@ pub fn ConfigElement( {name}
{children()}
+ {aside} } } diff --git a/src/app/header.rs b/src/app/header.rs index ce5a17e..cb7c45e 100644 --- a/src/app/header.rs +++ b/src/app/header.rs @@ -21,6 +21,7 @@ use crate::{ tools_menu::ToolsMenu, vfs_menu::VfsMenu, }, + send_request, }; istyles!(styles, "assets/module.postcss/header.module.css.map"); @@ -90,8 +91,9 @@ pub fn execute(state: Store) -> Box { let run_selected_code = !selected_code.is_empty() && state.run_selected_sql().get_untracked(); - if let Some(worker) = &*state.worker().read_untracked() { - worker.send_task(WorkerRequest::Run(RunOptions { + send_request( + state, + WorkerRequest::Run(RunOptions { embed: false, sql: if run_selected_code { selected_code @@ -99,8 +101,8 @@ pub fn execute(state: Store) -> Box { code }, clear_on_prepare: !*state.keep_ctx().read_untracked(), - })); - } + }), + ); }) } @@ -248,13 +250,14 @@ fn ToolsButton(menu_container: NodeRef) -> impl IntoView { let sql = editor.get_value(); drop(editor_guard); - if let Some(worker) = &*state.worker().read_untracked() { - worker.send_task(WorkerRequest::Run(RunOptions { + send_request( + state, + WorkerRequest::Run(RunOptions { embed: true, sql, clear_on_prepare: !*state.keep_ctx().read_untracked(), - })); - } + }), + ); signal.set(false); }; @@ -339,9 +342,7 @@ fn DatabaseButton( let result = reader.result().unwrap(); let array_buffer = result.unchecked_into::(); let data = js_sys::Uint8Array::new(&array_buffer); - if let Some(worker) = &*state.worker().read_untracked() { - worker.send_task(WorkerRequest::LoadDb(LoadDbOptions { data })); - } + send_request(state, WorkerRequest::LoadDb(LoadDbOptions { data })); }) as Box)); @@ -415,9 +416,7 @@ fn DatabaseButton( }); let on_download = move |_: MouseEvent, signal: WriteSignal| { - if let Some(worker) = &*state.worker().read() { - worker.send_task(WorkerRequest::DownloadDb); - } + send_request(state, WorkerRequest::DownloadDb); signal.set(false); }; diff --git a/src/app/mod.rs b/src/app/mod.rs index f5390ad..a22083e 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -21,5 +21,5 @@ mod state; mod tools_menu; mod vfs_menu; -pub use playground::playground; +pub use playground::Playground; pub use state::*; diff --git a/src/app/playground.rs b/src/app/playground.rs index c37e4e4..19f4ac3 100644 --- a/src/app/playground.rs +++ b/src/app/playground.rs @@ -6,9 +6,7 @@ use leptos::tachys::html; use prettytable::{Cell, Row, Table}; use reactive_stores::Store; use split_grid::{Gutter, SplitOptions}; -use tokio::sync::mpsc::UnboundedReceiver; use wasm_bindgen::{JsCast, prelude::Closure}; -use wasm_bindgen_futures::spawn_local; use web_sys::wasm_bindgen::JsValue; use crate::{ @@ -18,43 +16,34 @@ use crate::{ editor::Editor, header::Header, output::{Output, change_focus}, - state::{GlobalState, GlobalStateStoreFields, Orientation, Theme, Vfs}, + state::{GlobalState, GlobalStateStoreFields, Orientation, Theme}, }, }; -use crate::{WorkerHandle, WorkerResponse, handle_state}; istyles!(styles, "assets/module.postcss/playground.module.css.map"); -pub fn playground( - worker: (WorkerHandle, UnboundedReceiver), -) -> Box AnyView + 'static> { - Box::new(move || { - let (worker_handle, rx) = worker; - let state = GlobalState::load().unwrap_or_default(); - provide_context(Store::new(state)); +#[component] +pub fn Playground() -> impl IntoView { + let state = GlobalState::load().unwrap_or_default(); + provide_context(Store::new(state)); - let state = expect_context::>(); - state.worker().set(Some(worker_handle)); + let state = expect_context::>(); - handle_last_error(state); - handle_system_theme(state); - handle_automic_orientation(state); - handle_connect_db(state); - handle_save_state(state); - handle_import_progress(state); - handle_ace_config(state); - handle_embed_query_result(state); + handle_last_error(state); + handle_system_theme(state); + handle_automic_orientation(state); + handle_save_state(state); + handle_import_progress(state); + handle_ace_config(state); + handle_embed_query_result(state); - spawn_local(handle_state(state, rx)); - - view! { -
-
- -
- } - .into_any() - }) + view! { +
+
+ +
+ } + .into_any() } fn handle_embed_query_result(state: Store) { @@ -154,22 +143,12 @@ fn handle_save_state(state: Store) { state.keep_ctx().track(); state.sql().track(); state.run_selected_sql().track(); + state.multiple_ciphers().track(); state.read_untracked().save(); }); } -fn handle_connect_db(state: Store) { - Effect::new(move || { - if let Some(worker) = &*state.worker().read() { - worker.send_task(crate::WorkerRequest::Open(crate::OpenOptions { - filename: "test.db".into(), - persist: *state.vfs().read() == Vfs::OPFS, - })); - } - }); -} - fn handle_last_error(state: Store) { Effect::new(move || { if state.last_error().read().is_some() { diff --git a/src/app/state.rs b/src/app/state.rs index e40220c..66d3054 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -7,7 +7,7 @@ use reactive_stores::Store; use serde::{Deserialize, Serialize}; use web_sys::MediaQueryList; -use crate::{FragileComfirmed, SQLightError, SQLiteStatementResult, WorkerHandle}; +use crate::{FragileComfirmed, SQLightError, SQLiteStatementResult}; const DEFAULT_CODE: &str = "PRAGMA page_size=4096; @@ -34,10 +34,9 @@ pub struct GlobalState { keep_ctx: bool, sql: String, run_selected_sql: bool, + multiple_ciphers: bool, // runtime state below #[serde(skip)] - worker: Option, - #[serde(skip)] editor: Option, #[serde(skip)] focus: Option, @@ -71,7 +70,7 @@ impl Default for GlobalState { keep_ctx: false, sql: DEFAULT_CODE.into(), run_selected_sql: false, - worker: None, + multiple_ciphers: false, editor: None, focus: None, is_focused: false, diff --git a/src/bin/app.rs b/src/bin/app.rs index 0e32b37..a2d7073 100644 --- a/src/bin/app.rs +++ b/src/bin/app.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use sqlight::{app::playground, setup_worker}; +use sqlight::app::Playground; use wasm_bindgen::prelude::wasm_bindgen; @@ -7,5 +7,5 @@ use wasm_bindgen::prelude::wasm_bindgen; async fn main() { console_error_panic_hook::set_once(); console_log::init_with_level(log::Level::Debug).unwrap(); - mount_to_body(playground(setup_worker().await)); + mount_to_body(Playground); } diff --git a/src/bin/sqlite3.rs b/src/bin/sqlite3.rs new file mode 100644 index 0000000..173dee1 --- /dev/null +++ b/src/bin/sqlite3.rs @@ -0,0 +1,3 @@ +fn main() { + sqlight::worker::entry(); +} diff --git a/src/bin/sqlite3mc.rs b/src/bin/sqlite3mc.rs new file mode 100644 index 0000000..173dee1 --- /dev/null +++ b/src/bin/sqlite3mc.rs @@ -0,0 +1,3 @@ +fn main() { + sqlight::worker::entry(); +} diff --git a/src/bin/worker.rs b/src/bin/worker.rs deleted file mode 100644 index 2799ab7..0000000 --- a/src/bin/worker.rs +++ /dev/null @@ -1,42 +0,0 @@ -use sqlight::{WorkerRequest, WorkerResponse, worker}; -use tokio::sync::mpsc::UnboundedReceiver; -use wasm_bindgen::{JsCast, JsValue, prelude::Closure}; -use wasm_bindgen_futures::spawn_local; -use web_sys::{DedicatedWorkerGlobalScope, MessageEvent}; - -fn main() { - console_error_panic_hook::set_once(); - console_log::init_with_level(log::Level::Debug).unwrap(); - - let (tx, rx) = tokio::sync::mpsc::unbounded_channel::(); - - let scope: DedicatedWorkerGlobalScope = JsValue::from(js_sys::global()).into(); - spawn_local(execute_task(scope.clone(), rx)); - - let on_message = Closure::::new(move |ev: MessageEvent| { - tx.send(ev.data()).unwrap(); - }); - - scope.set_onmessage(Some(on_message.as_ref().unchecked_ref())); - scope - .post_message(&serde_wasm_bindgen::to_value(&WorkerResponse::Ready).unwrap()) - .expect("Faild to send ready to window"); - on_message.forget(); -} - -async fn execute_task(scope: DedicatedWorkerGlobalScope, mut rx: UnboundedReceiver) { - while let Some(request) = rx.recv().await { - let request = serde_wasm_bindgen::from_value::(request).unwrap(); - let resp = match request { - WorkerRequest::Open(options) => WorkerResponse::Open(worker::open(options).await), - WorkerRequest::Run(options) => WorkerResponse::Run(worker::run(options).await), - WorkerRequest::LoadDb(options) => { - WorkerResponse::LoadDb(worker::load_db(options).await) - } - WorkerRequest::DownloadDb => WorkerResponse::DownloadDb(worker::download_db().await), - }; - if let Err(err) = scope.post_message(&serde_wasm_bindgen::to_value(&resp).unwrap()) { - log::error!("Failed to send task to window: {resp:?}, {err:?}"); - } - } -} diff --git a/src/lib.rs b/src/lib.rs index f521e52..ffece97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,20 @@ pub mod app; +#[cfg(any(feature = "sqlite3", feature = "sqlite3mc"))] pub mod worker; use aceditor::EditorError; -use app::{Exported, GlobalState, GlobalStateStoreFields}; +use app::{Exported, GlobalState, GlobalStateStoreFields, Vfs}; use fragile::Fragile; use js_sys::Uint8Array; use leptos::prelude::*; use reactive_stores::Store; use std::{ ops::{Deref, DerefMut}, - sync::Arc, + sync::{Arc, Once}, }; -use tokio::sync::mpsc::UnboundedReceiver; +use tokio::sync::{OnceCell, mpsc::UnboundedReceiver}; use wasm_bindgen::{JsCast, prelude::Closure}; +use wasm_bindgen_futures::spawn_local; use web_sys::{MessageEvent, Worker, WorkerOptions, WorkerType}; use serde::{Deserialize, Serialize}; @@ -51,8 +53,6 @@ impl DerefMut for FragileComfirmed { } } -pub const PERSIST_VFS: &str = "sqlight-sahpool"; - #[derive(thiserror::Error, Debug)] pub enum SQLightError { #[error(transparent)] @@ -127,16 +127,6 @@ pub struct LoadDbOptions { pub data: Uint8Array, } -impl OpenOptions { - pub fn uri(&self) -> String { - format!( - "file:{}?vfs={}", - self.filename, - if self.persist { PERSIST_VFS } else { "memvfs" } - ) - } -} - #[derive(Debug, Serialize, Deserialize)] pub struct RunOptions { pub sql: String, @@ -209,9 +199,55 @@ impl WorkerHandle { unsafe impl Send for WorkerHandle {} unsafe impl Sync for WorkerHandle {} -pub async fn setup_worker() -> (WorkerHandle, UnboundedReceiver) { - let uri = "./worker_loader.js"; +fn send_request(state: Store, req: WorkerRequest) { + spawn_local(async move { + if state.multiple_ciphers().get_untracked() { + sqlite3mc(state).await + } else { + sqlite3(state).await + } + .send_task(req); + }); +} +async fn sqlite3mc(state: Store) -> &'static WorkerHandle { + static ONCE: Once = Once::new(); + static WORKER: OnceCell = OnceCell::const_new(); + + let worker = WORKER + .get_or_init(|| async { setup_worker(state, "./sqlite3mc_loader.js").await }) + .await; + + ONCE.call_once(|| { + connect_db(state, worker); + Effect::new(move || connect_db(state, worker)); + }); + worker +} + +async fn sqlite3(state: Store) -> &'static WorkerHandle { + static ONCE: Once = Once::new(); + static WORKER: OnceCell = OnceCell::const_new(); + + let worker = WORKER + .get_or_init(|| async { setup_worker(state, "./sqlite3_loader.js").await }) + .await; + + ONCE.call_once(|| { + connect_db(state, worker); + Effect::new(move || connect_db(state, worker)); + }); + worker +} + +fn connect_db(state: Store, handle: &'static WorkerHandle) { + handle.send_task(crate::WorkerRequest::Open(crate::OpenOptions { + filename: "test.db".into(), + persist: *state.vfs().read() == Vfs::OPFS, + })); +} + +async fn setup_worker(state: Store, uri: &str) -> WorkerHandle { let opts = WorkerOptions::new(); opts.set_type(WorkerType::Module); @@ -233,14 +269,16 @@ pub async fn setup_worker() -> (WorkerHandle, UnboundedReceiver) } }); + spawn_local(handle_state(state, rx)); + worker.set_onmessage(Some(on_message.as_ref().unchecked_ref())); on_message.forget(); wait.notified().await; - (WorkerHandle(worker), rx) + WorkerHandle(worker) } -pub async fn handle_state(state: Store, mut rx: UnboundedReceiver) { +async fn handle_state(state: Store, mut rx: UnboundedReceiver) { while let Some(resp) = rx.recv().await { state.last_error().set(None); diff --git a/src/worker/mod.rs b/src/worker/mod.rs index b37866a..79ebc85 100644 --- a/src/worker/mod.rs +++ b/src/worker/mod.rs @@ -1,8 +1,8 @@ mod sqlitend; use crate::{ - DownloadDbResponse, LoadDbOptions, OpenOptions, PERSIST_VFS, RunOptions, SQLiteRunResult, - WorkerError, + DownloadDbResponse, LoadDbOptions, OpenOptions, RunOptions, SQLiteRunResult, WorkerError, + WorkerRequest, WorkerResponse, }; use once_cell::sync::Lazy; use sqlite_wasm_rs::{ @@ -14,6 +14,10 @@ use sqlitend::SQLiteDb; use std::sync::Arc; use tokio::sync::Mutex; use tokio::sync::OnceCell; +use tokio::sync::mpsc::UnboundedReceiver; +use wasm_bindgen::{JsCast, JsValue, prelude::Closure}; +use wasm_bindgen_futures::spawn_local; +use web_sys::{DedicatedWorkerGlobalScope, MessageEvent}; type Result = std::result::Result; @@ -24,6 +28,29 @@ static FS_UTIL: Lazy = Lazy::new(|| FSUtil { opfs: OnceCell::new(), }); +#[cfg(feature = "sqlite3")] +const OPFS_VFS: &str = "opfs"; +#[cfg(feature = "sqlite3mc")] +const OPFS_VFS: &str = "multipleciphers-opfs"; + +#[cfg(feature = "sqlite3")] +const OPFS_VFS_DIR: &str = "sqlight-sahpool"; +#[cfg(feature = "sqlite3mc")] +const OPFS_VFS_DIR: &str = "sqlight-sahpool-mc"; + +#[cfg(feature = "sqlite3")] +const MEM_VFS: &str = "memvfs"; +#[cfg(feature = "sqlite3mc")] +const MEM_VFS: &str = "multipleciphers-memvfs"; + +fn uri(filename: &str, persist: bool) -> String { + format!( + "file:{}?vfs={}", + filename, + if persist { OPFS_VFS } else { MEM_VFS } + ) +} + struct FSUtil { mem: MemVfsUtil, opfs: OnceCell, @@ -53,8 +80,8 @@ async fn init_opfs_util() -> Result<&'static OpfsSAHPoolUtil> { sqlite_wasm_rs::sahpool_vfs::install( Some( &OpfsSAHPoolCfgBuilder::new() - .directory(PERSIST_VFS) - .vfs_name(PERSIST_VFS) + .directory(OPFS_VFS_DIR) + .vfs_name("opfs") .build(), ), false, @@ -69,7 +96,7 @@ fn get_opfs_util() -> Result<&'static OpfsSAHPoolUtil> { FS_UTIL.opfs.get().ok_or(WorkerError::Unexpected) } -pub async fn download_db() -> Result { +async fn download_db() -> Result { with_worker(|worker| { let filename = &worker.open_options.filename; let db = if worker.open_options.persist { @@ -90,34 +117,42 @@ pub async fn download_db() -> Result { .await } -pub async fn load_db(options: LoadDbOptions) -> Result<()> { +async fn load_db(options: LoadDbOptions) -> Result<()> { let db = copy_to_vec(&options.data); + #[cfg(feature = "sqlite3")] + sqlite_wasm_rs::utils::check_import_db(&db) + .map_err(|err| WorkerError::LoadDb(format!("{err}")))?; + with_worker(|worker| { - let _ = std::mem::replace(&mut worker.state, SQLiteState::NotOpened); + drop(std::mem::replace(&mut worker.state, SQLiteState::NotOpened)); let filename = &worker.open_options.filename; if worker.open_options.persist { let opfs = get_opfs_util()?; opfs.unlink(filename).map_err(|_| WorkerError::Unexpected)?; - if let Err(err) = opfs.import_db(filename, &db) { + + if let Err(err) = opfs.import_db_unchecked(filename, &db) { return Err(WorkerError::LoadDb(format!("{err}"))); } } else { let mem_vfs = &FS_UTIL.mem; mem_vfs.delete_db(filename); - if let Err(err) = mem_vfs.import_db(filename, &db) { + if let Err(err) = mem_vfs.import_db_unchecked(filename, &db, 114514 /* unused */) { return Err(WorkerError::LoadDb(format!("{err}"))); } } - worker.state = SQLiteState::Opened(SQLiteDb::open(&worker.open_options.uri())?); + worker.state = SQLiteState::Opened(SQLiteDb::open(&uri( + &worker.open_options.filename, + worker.open_options.persist, + ))?); Ok(()) }) .await } -pub async fn open(options: OpenOptions) -> Result<()> { +async fn open(options: OpenOptions) -> Result<()> { let mut locker = DB.lock().await; locker.take(); @@ -125,7 +160,7 @@ pub async fn open(options: OpenOptions) -> Result<()> { init_opfs_util().await?; } - let state = SQLiteState::Opened(SQLiteDb::open(&options.uri())?); + let state = SQLiteState::Opened(SQLiteDb::open(&uri(&options.filename, options.persist))?); let worker = SQLiteWorker { open_options: options, state, @@ -134,10 +169,10 @@ pub async fn open(options: OpenOptions) -> Result<()> { Ok(()) } -pub async fn run(options: RunOptions) -> Result { +async fn run(options: RunOptions) -> Result { with_worker(|worker| { if options.clear_on_prepare { - let _ = std::mem::replace(&mut worker.state, SQLiteState::NotOpened); + drop(std::mem::replace(&mut worker.state, SQLiteState::NotOpened)); let filename = &worker.open_options.filename; if worker.open_options.persist { @@ -149,7 +184,10 @@ pub async fn run(options: RunOptions) -> Result { mem_vfs.delete_db(filename); } - worker.state = SQLiteState::Opened(SQLiteDb::open(&worker.open_options.uri())?); + worker.state = SQLiteState::Opened(SQLiteDb::open(&uri( + &worker.open_options.filename, + worker.open_options.persist, + ))?); } match &worker.state { SQLiteState::NotOpened => Err(WorkerError::InvaildState), @@ -165,3 +203,38 @@ pub async fn run(options: RunOptions) -> Result { }) .await } + +async fn execute_task(scope: DedicatedWorkerGlobalScope, mut rx: UnboundedReceiver) { + while let Some(request) = rx.recv().await { + let request = serde_wasm_bindgen::from_value::(request).unwrap(); + let resp = match request { + WorkerRequest::Open(options) => WorkerResponse::Open(open(options).await), + WorkerRequest::Run(options) => WorkerResponse::Run(run(options).await), + WorkerRequest::LoadDb(options) => WorkerResponse::LoadDb(load_db(options).await), + WorkerRequest::DownloadDb => WorkerResponse::DownloadDb(download_db().await), + }; + if let Err(err) = scope.post_message(&serde_wasm_bindgen::to_value(&resp).unwrap()) { + log::error!("Failed to send task to window: {resp:?}, {err:?}"); + } + } +} + +pub fn entry() { + console_error_panic_hook::set_once(); + console_log::init_with_level(log::Level::Debug).unwrap(); + + let (tx, rx) = tokio::sync::mpsc::unbounded_channel::(); + + let scope: DedicatedWorkerGlobalScope = JsValue::from(js_sys::global()).into(); + spawn_local(execute_task(scope.clone(), rx)); + + let on_message = Closure::::new(move |ev: MessageEvent| { + tx.send(ev.data()).unwrap(); + }); + + scope.set_onmessage(Some(on_message.as_ref().unchecked_ref())); + scope + .post_message(&serde_wasm_bindgen::to_value(&WorkerResponse::Ready).unwrap()) + .expect("Faild to send ready to window"); + on_message.forget(); +}