Add SQLite3MultipleCiphers support
This commit is contained in:
6
.github/workflows/pages.yaml
vendored
6
.github/workflows/pages.yaml
vendored
@@ -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
30
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -21,5 +21,5 @@ mod state;
|
||||
mod tools_menu;
|
||||
mod vfs_menu;
|
||||
|
||||
pub use playground::playground;
|
||||
pub use playground::Playground;
|
||||
pub use state::*;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
3
src/bin/sqlite3.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
sqlight::worker::entry();
|
||||
}
|
||||
3
src/bin/sqlite3mc.rs
Normal file
3
src/bin/sqlite3mc.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
sqlight::worker::entry();
|
||||
}
|
||||
@@ -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:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
76
src/lib.rs
76
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<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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user