Add SQLite3MultipleCiphers support

This commit is contained in:
Spxg
2025-05-29 22:44:39 +08:00
parent 81561cb51d
commit 09d499d5a3
16 changed files with 264 additions and 147 deletions

View File

@@ -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

30
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"]

View File

@@ -53,8 +53,9 @@
<link data-trunk href="./assets/module.postcss/output/section.module.css" rel="css">
<link data-trunk href="./assets/module.postcss/output/share.module.css" rel="css">
<link data-trunk rel="rust" href="Cargo.toml" data-bin="app" data-type="main" />
<link data-trunk rel="rust" href="Cargo.toml" data-bin="worker" data-type="worker" data-loader-shim data-bindgen-target="web" />
<link data-trunk rel="rust" href="Cargo.toml" data-cargo-no-default-features data-bin="app" data-type="main" />
<link data-trunk rel="rust" href="Cargo.toml" data-cargo-no-default-features data-cargo-features="sqlite3" data-bin="sqlite3" data-type="worker" data-loader-shim data-bindgen-target="web" />
<link data-trunk rel="rust" href="Cargo.toml" data-cargo-no-default-features data-cargo-features="sqlite3mc" data-bin="sqlite3mc" data-type="worker" data-loader-shim data-bindgen-target="web" />
</head>
<body></body>
</html>

View File

@@ -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);
}
/>
<Either
aside=Some(
view! {
<MenuAside>
"Using an encrypted database.
https://github.com/utelle/SQLite3MultipleCiphers
https://utelle.github.io/SQLite3MultipleCiphers"
</MenuAside>
}
.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);
}
/>
</MenuGroup>
</>
}

View File

@@ -13,6 +13,7 @@ istyles!(
#[component]
pub fn Either<E, V, T>(
#[prop(default =None)] aside: Option<AnyView>,
on_change: E,
id: String,
a: T,
@@ -58,7 +59,7 @@ where
let b2 = Arc::clone(&b1);
view! {
<ConfigElement name=name is_default=is_default>
<ConfigElement name=name is_default=is_default aside>
<div class=styles::toggle>
<input
id=a_id.clone()
@@ -105,6 +106,7 @@ where
#[component]
pub fn ConfigElement(
name: String,
#[prop(default =None)] aside: Option<AnyView>,
#[prop(default = Box::new(|| true))] is_default: Box<dyn Fn() -> bool + Send>,
children: Children,
) -> impl IntoView {
@@ -121,6 +123,7 @@ pub fn ConfigElement(
<span class=style>{name}</span>
<div class=styles::value>{children()}</div>
</div>
{aside}
</MenuItem>
}
}

View File

@@ -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<GlobalState>) -> Box<dyn Fn() + Send + 'static> {
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<GlobalState>) -> Box<dyn Fn() + Send + 'static> {
code
},
clear_on_prepare: !*state.keep_ctx().read_untracked(),
}));
}
}),
);
})
}
@@ -248,13 +250,14 @@ fn ToolsButton(menu_container: NodeRef<html::element::Div>) -> 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::<js_sys::ArrayBuffer>();
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<dyn FnMut(_)>));
@@ -415,9 +416,7 @@ fn DatabaseButton(
});
let on_download = move |_: MouseEvent, signal: WriteSignal<bool>| {
if let Some(worker) = &*state.worker().read() {
worker.send_task(WorkerRequest::DownloadDb);
}
send_request(state, WorkerRequest::DownloadDb);
signal.set(false);
};

View File

@@ -21,5 +21,5 @@ mod state;
mod tools_menu;
mod vfs_menu;
pub use playground::playground;
pub use playground::Playground;
pub use state::*;

View File

@@ -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<WorkerResponse>),
) -> Box<dyn FnOnce() -> 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::<Store<GlobalState>>();
state.worker().set(Some(worker_handle));
let state = expect_context::<Store<GlobalState>>();
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! {
<div id="playground" class=styles::container>
<Header />
<ResizableArea />
</div>
}
.into_any()
})
view! {
<div id="playground" class=styles::container>
<Header />
<ResizableArea />
</div>
}
.into_any()
}
fn handle_embed_query_result(state: Store<GlobalState>) {
@@ -154,22 +143,12 @@ fn handle_save_state(state: Store<GlobalState>) {
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<GlobalState>) {
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<GlobalState>) {
Effect::new(move || {
if state.last_error().read().is_some() {

View File

@@ -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<WorkerHandle>,
#[serde(skip)]
editor: Option<Editor>,
#[serde(skip)]
focus: Option<Focus>,
@@ -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,

View File

@@ -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);
}

3
src/bin/sqlite3.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
sqlight::worker::entry();
}

3
src/bin/sqlite3mc.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
sqlight::worker::entry();
}

View File

@@ -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::<JsValue>();
let scope: DedicatedWorkerGlobalScope = JsValue::from(js_sys::global()).into();
spawn_local(execute_task(scope.clone(), rx));
let on_message = Closure::<dyn Fn(MessageEvent)>::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<JsValue>) {
while let Some(request) = rx.recv().await {
let request = serde_wasm_bindgen::from_value::<WorkerRequest>(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:?}");
}
}
}

View File

@@ -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<T> DerefMut for FragileComfirmed<T> {
}
}
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<WorkerResponse>) {
let uri = "./worker_loader.js";
fn send_request(state: Store<GlobalState>, 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<GlobalState>) -> &'static WorkerHandle {
static ONCE: Once = Once::new();
static WORKER: OnceCell<WorkerHandle> = 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<GlobalState>) -> &'static WorkerHandle {
static ONCE: Once = Once::new();
static WORKER: OnceCell<WorkerHandle> = 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<GlobalState>, 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<GlobalState>, uri: &str) -> WorkerHandle {
let opts = WorkerOptions::new();
opts.set_type(WorkerType::Module);
@@ -233,14 +269,16 @@ pub async fn setup_worker() -> (WorkerHandle, UnboundedReceiver<WorkerResponse>)
}
});
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<GlobalState>, mut rx: UnboundedReceiver<WorkerResponse>) {
async fn handle_state(state: Store<GlobalState>, mut rx: UnboundedReceiver<WorkerResponse>) {
while let Some(resp) = rx.recv().await {
state.last_error().set(None);

View File

@@ -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<T> = std::result::Result<T, WorkerError>;
@@ -24,6 +28,29 @@ static FS_UTIL: Lazy<FSUtil> = 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<OpfsSAHPoolUtil>,
@@ -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<DownloadDbResponse> {
async fn download_db() -> Result<DownloadDbResponse> {
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<DownloadDbResponse> {
.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<SQLiteRunResult> {
async fn run(options: RunOptions) -> Result<SQLiteRunResult> {
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<SQLiteRunResult> {
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<SQLiteRunResult> {
})
.await
}
async fn execute_task(scope: DedicatedWorkerGlobalScope, mut rx: UnboundedReceiver<JsValue>) {
while let Some(request) = rx.recv().await {
let request = serde_wasm_bindgen::from_value::<WorkerRequest>(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::<JsValue>();
let scope: DedicatedWorkerGlobalScope = JsValue::from(js_sys::global()).into();
spawn_local(execute_task(scope.clone(), rx));
let on_message = Closure::<dyn Fn(MessageEvent)>::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();
}