Add import and load db support
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
use istyles::istyles;
|
use istyles::istyles;
|
||||||
use leptos::{prelude::*, tachys::html};
|
use leptos::{html::Input, prelude::*, tachys::html};
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
use web_sys::{Url, UrlSearchParams};
|
use wasm_bindgen::{JsCast, prelude::Closure};
|
||||||
|
use web_sys::{Event, FileReader, HtmlInputElement, Url, UrlSearchParams};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PrepareOptions, WorkerRequest,
|
FragileComfirmed, LoadDbOptions, PrepareOptions, SQLightError, WorkerRequest,
|
||||||
app::{
|
app::{
|
||||||
|
ImportProgress, Vfs,
|
||||||
advanced_options_menu::AdvancedOptionsMenu,
|
advanced_options_menu::AdvancedOptionsMenu,
|
||||||
button_set::{Button, ButtonSet, IconButton, Rule},
|
button_set::{Button, ButtonSet, IconButton, Rule},
|
||||||
config_menu::ConfigMenu,
|
config_menu::ConfigMenu,
|
||||||
@@ -24,6 +26,8 @@ istyles!(styles, "assets/module.postcss/header.module.css.map");
|
|||||||
pub fn Header() -> impl IntoView {
|
pub fn Header() -> impl IntoView {
|
||||||
let menu_container = NodeRef::new();
|
let menu_container = NodeRef::new();
|
||||||
|
|
||||||
|
let input_ref = NodeRef::new();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<>
|
<>
|
||||||
<div id="header" class=styles::container>
|
<div id="header" class=styles::container>
|
||||||
@@ -41,6 +45,10 @@ pub fn Header() -> impl IntoView {
|
|||||||
</ButtonSet>
|
</ButtonSet>
|
||||||
</div>
|
</div>
|
||||||
<div class=styles::right>
|
<div class=styles::right>
|
||||||
|
<input type="file" node_ref=input_ref style="display: none" />
|
||||||
|
<ButtonSet>
|
||||||
|
<LoadButton input_ref=input_ref />
|
||||||
|
</ButtonSet>
|
||||||
<ButtonSet>
|
<ButtonSet>
|
||||||
<ShareButton />
|
<ShareButton />
|
||||||
</ButtonSet>
|
</ButtonSet>
|
||||||
@@ -99,6 +107,108 @@ fn ExecuteButton() -> impl IntoView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn LoadButton(input_ref: NodeRef<Input>) -> impl IntoView {
|
||||||
|
let state = expect_context::<Store<GlobalState>>();
|
||||||
|
|
||||||
|
let (file, set_file) = signal::<Option<FragileComfirmed<web_sys::File>>>(None);
|
||||||
|
|
||||||
|
Effect::new(move || {
|
||||||
|
if let Some(file) = &*file.read() {
|
||||||
|
let filename = file.name();
|
||||||
|
|
||||||
|
if let Ok(reader) = FileReader::new() {
|
||||||
|
let on_progress = FragileComfirmed::new(Closure::wrap(Box::new(
|
||||||
|
move |ev: web_sys::ProgressEvent| {
|
||||||
|
if ev.length_computable() {
|
||||||
|
state.import_progress().set(Some(ImportProgress {
|
||||||
|
filename: filename.clone(),
|
||||||
|
loaded: ev.loaded(),
|
||||||
|
total: ev.total(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
as Box<dyn FnMut(_)>));
|
||||||
|
|
||||||
|
let on_error = FragileComfirmed::new(Closure::wrap(Box::new(
|
||||||
|
move |ev: web_sys::ProgressEvent| {
|
||||||
|
if let Some(target) = ev.target() {
|
||||||
|
let reader = target.unchecked_into::<FileReader>();
|
||||||
|
if let Some(dom_error) = reader.error() {
|
||||||
|
state.last_error().set(Some(FragileComfirmed::new(
|
||||||
|
SQLightError::ImportDb(dom_error.message().to_string()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
as Box<dyn FnMut(_)>));
|
||||||
|
|
||||||
|
let on_load =
|
||||||
|
FragileComfirmed::new(Closure::wrap(Box::new(move |ev: web_sys::Event| {
|
||||||
|
if let Some(target) = ev.target() {
|
||||||
|
let reader = target.unchecked_into::<FileReader>();
|
||||||
|
if let Ok(result) = reader.result() {
|
||||||
|
let array_buffer = result.unchecked_into::<js_sys::ArrayBuffer>();
|
||||||
|
let data = js_sys::Uint8Array::new(&array_buffer);
|
||||||
|
let persist = state.vfs().get() != Vfs::Memory;
|
||||||
|
if let Some(worker) = &*state.worker().read() {
|
||||||
|
worker.send_task(WorkerRequest::LoadDb(LoadDbOptions {
|
||||||
|
// FIXME: multi db
|
||||||
|
id: String::new(),
|
||||||
|
persist,
|
||||||
|
data,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
as Box<dyn FnMut(_)>));
|
||||||
|
|
||||||
|
reader.set_onprogress(Some(on_progress.as_ref().unchecked_ref()));
|
||||||
|
reader.set_onerror(Some(on_error.as_ref().unchecked_ref()));
|
||||||
|
reader.set_onload(Some(on_load.as_ref().unchecked_ref()));
|
||||||
|
reader.read_as_array_buffer(file).unwrap();
|
||||||
|
|
||||||
|
on_cleanup(move || {
|
||||||
|
drop(on_progress);
|
||||||
|
drop(on_error);
|
||||||
|
drop(on_load);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let on_change = move |ev: Event| {
|
||||||
|
if let Some(target) = ev.target() {
|
||||||
|
let input = target.unchecked_into::<HtmlInputElement>();
|
||||||
|
if let Some(files) = input.files() {
|
||||||
|
if files.length() > 0 {
|
||||||
|
let file = FragileComfirmed::new(files.get(0).unwrap());
|
||||||
|
set_file.set(Some(file));
|
||||||
|
} else {
|
||||||
|
set_file.set(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_click = move |_| {
|
||||||
|
if let Some(input) = &*input_ref.read() {
|
||||||
|
if input.onchange().is_none() {
|
||||||
|
let callback = Closure::wrap(Box::new(on_change) as Box<dyn Fn(Event)>);
|
||||||
|
input.set_onchange(Some(callback.as_ref().unchecked_ref::<js_sys::Function>()));
|
||||||
|
callback.forget();
|
||||||
|
}
|
||||||
|
input.set_value("");
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
view! { <Button on_click=on_click>"Load DB"</Button> }
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn VfsMenuButton(menu_container: NodeRef<html::element::Div>) -> impl IntoView {
|
fn VfsMenuButton(menu_container: NodeRef<html::element::Div>) -> impl IntoView {
|
||||||
let state = expect_context::<Store<GlobalState>>();
|
let state = expect_context::<Store<GlobalState>>();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub fn Section(label: String, children: Children) -> impl IntoView {
|
|||||||
view! {
|
view! {
|
||||||
<div>
|
<div>
|
||||||
<Header label=label />
|
<Header label=label />
|
||||||
{children()}
|
<p>{children()}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Please close other tabs and refresh, or switch to Memory VFS.";
|
|||||||
pub fn Status() -> impl IntoView {
|
pub fn Status() -> impl IntoView {
|
||||||
let state = expect_context::<Store<GlobalState>>();
|
let state = expect_context::<Store<GlobalState>>();
|
||||||
|
|
||||||
let show = move || match &*state.last_error().read() {
|
let last_error = move || match &*state.last_error().read() {
|
||||||
Some(error) => {
|
Some(error) => {
|
||||||
let summary = format!("{}", error.deref());
|
let summary = format!("{}", error.deref());
|
||||||
let details = match error.deref() {
|
let details = match error.deref() {
|
||||||
@@ -42,12 +42,15 @@ pub fn Status() -> impl IntoView {
|
|||||||
"An unsupported type was encountered, please create an issue on github."
|
"An unsupported type was encountered, please create an issue on github."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
WorkerError::NotFound | WorkerError::OpfsSAHError => {
|
WorkerError::NotFound | WorkerError::Unexpected => {
|
||||||
"This shouldn't happen, please create an issue on github."
|
"This shouldn't happen, please create an issue on github."
|
||||||
}
|
}
|
||||||
WorkerError::InvaildState => {
|
WorkerError::InvaildState => {
|
||||||
"SQLite is in an abnormal state when executing SQLite."
|
"SQLite is in an abnormal state when executing SQLite."
|
||||||
}
|
}
|
||||||
|
WorkerError::LoadDb(_) => {
|
||||||
|
"Please check whether the imported DB is a SQLite3 file"
|
||||||
|
}
|
||||||
WorkerError::OpfsSAHPoolOpened => OPFS_SAH_POOL_OPENED_DETAILS,
|
WorkerError::OpfsSAHPoolOpened => OPFS_SAH_POOL_OPENED_DETAILS,
|
||||||
},
|
},
|
||||||
SQLightError::AceEditor(ace_editor) => match ace_editor {
|
SQLightError::AceEditor(ace_editor) => match ace_editor {
|
||||||
@@ -59,6 +62,9 @@ pub fn Status() -> impl IntoView {
|
|||||||
"This shouldn't happen, please create an issue on github."
|
"This shouldn't happen, please create an issue on github."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
SQLightError::ImportDb(_) => {
|
||||||
|
"Maybe the db was not found, could not be read, or was too large."
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
@@ -72,9 +78,28 @@ pub fn Status() -> impl IntoView {
|
|||||||
None => view! { "No Error" }.into_any(),
|
None => view! { "No Error" }.into_any(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let import_progress = move || {
|
||||||
|
if let Some(progress) = &*state.import_progress().read() {
|
||||||
|
let filename = format!("Filename: {}", progress.filename);
|
||||||
|
let status = format!("Loading: {} of {} bytes.", progress.loaded, progress.total);
|
||||||
|
view! {
|
||||||
|
<p>{filename}</p>
|
||||||
|
<p>{status}</p>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
} else {
|
||||||
|
view! { "No files are being imported." }.into_any()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<SimplePane>
|
<SimplePane>
|
||||||
<Section label="Last Error".into()>{show}</Section>
|
<Section label="Last Error".into()>
|
||||||
|
<pre style="white-space: pre-wrap;">{last_error}</pre>
|
||||||
|
</Section>
|
||||||
|
<Section label="Import Progress".into()>
|
||||||
|
<pre style="white-space: pre-wrap;">{import_progress}</pre>
|
||||||
|
</Section>
|
||||||
</SimplePane>
|
</SimplePane>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ pub fn playground(
|
|||||||
handle_automic_orientation(state);
|
handle_automic_orientation(state);
|
||||||
handle_connect_db(state);
|
handle_connect_db(state);
|
||||||
hanlde_save_state(state);
|
hanlde_save_state(state);
|
||||||
|
handle_import_progress(state);
|
||||||
|
|
||||||
spawn_local(handle_state(state, rx));
|
spawn_local(handle_state(state, rx));
|
||||||
|
|
||||||
@@ -83,6 +84,14 @@ fn handle_last_error(state: Store<GlobalState>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_import_progress(state: Store<GlobalState>) {
|
||||||
|
Effect::new(move || {
|
||||||
|
if state.import_progress().read().is_some() {
|
||||||
|
change_focus(state, Some(Focus::Status));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_system_theme(state: Store<GlobalState>) {
|
fn handle_system_theme(state: Store<GlobalState>) {
|
||||||
Effect::new(move || {
|
Effect::new(move || {
|
||||||
let theme = match state.theme().read().value() {
|
let theme = match state.theme().read().value() {
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ pub struct GlobalState {
|
|||||||
output: Vec<SQLiteStatementResult>,
|
output: Vec<SQLiteStatementResult>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
last_error: Option<FragileComfirmed<SQLightError>>,
|
last_error: Option<FragileComfirmed<SQLightError>>,
|
||||||
|
#[serde(skip)]
|
||||||
|
import_progress: Option<ImportProgress>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GlobalState {
|
impl Default for GlobalState {
|
||||||
@@ -62,6 +64,7 @@ impl Default for GlobalState {
|
|||||||
show_something: false,
|
show_something: false,
|
||||||
output: Vec::new(),
|
output: Vec::new(),
|
||||||
last_error: None,
|
last_error: None,
|
||||||
|
import_progress: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,6 +88,12 @@ impl GlobalState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ImportProgress {
|
||||||
|
pub filename: String,
|
||||||
|
pub loaded: f64,
|
||||||
|
pub total: f64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct EditorConfig {
|
pub struct EditorConfig {
|
||||||
pub keyboard: String,
|
pub keyboard: String,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use web_sys::{DedicatedWorkerGlobalScope, MessageEvent};
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
console_log::init_with_level(log::Level::Warn).unwrap();
|
console_log::init_with_level(log::Level::Debug).unwrap();
|
||||||
|
|
||||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<JsValue>();
|
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<JsValue>();
|
||||||
|
|
||||||
@@ -34,6 +34,9 @@ async fn execute_task(scope: DedicatedWorkerGlobalScope, mut rx: UnboundedReceiv
|
|||||||
WorkerRequest::StepOver(id) => WorkerResponse::StepOver(worker::step_over(&id)),
|
WorkerRequest::StepOver(id) => WorkerResponse::StepOver(worker::step_over(&id)),
|
||||||
WorkerRequest::StepIn(id) => WorkerResponse::StepIn(worker::step_in(&id)),
|
WorkerRequest::StepIn(id) => WorkerResponse::StepIn(worker::step_in(&id)),
|
||||||
WorkerRequest::StepOut(id) => WorkerResponse::StepOut(worker::step_out(&id)),
|
WorkerRequest::StepOut(id) => WorkerResponse::StepOut(worker::step_out(&id)),
|
||||||
|
WorkerRequest::LoadDb(options) => {
|
||||||
|
WorkerResponse::LoadDb(worker::load_db(options).await)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if let Err(err) = scope.post_message(&serde_wasm_bindgen::to_value(&resp).unwrap()) {
|
if let Err(err) = scope.post_message(&serde_wasm_bindgen::to_value(&resp).unwrap()) {
|
||||||
log::error!("Failed to send task to window: {resp:?}, {err:?}");
|
log::error!("Failed to send task to window: {resp:?}, {err:?}");
|
||||||
|
|||||||
34
src/lib.rs
34
src/lib.rs
@@ -4,6 +4,7 @@ pub mod worker;
|
|||||||
use aceditor::EditorError;
|
use aceditor::EditorError;
|
||||||
use app::{GlobalState, GlobalStateStoreFields};
|
use app::{GlobalState, GlobalStateStoreFields};
|
||||||
use fragile::Fragile;
|
use fragile::Fragile;
|
||||||
|
use js_sys::Uint8Array;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -58,6 +59,8 @@ pub enum SQLightError {
|
|||||||
Worker(#[from] WorkerError),
|
Worker(#[from] WorkerError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
AceEditor(#[from] EditorError),
|
AceEditor(#[from] EditorError),
|
||||||
|
#[error("Failed to import db: {0}")]
|
||||||
|
ImportDb(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SQLightError {
|
impl SQLightError {
|
||||||
@@ -80,8 +83,10 @@ pub enum WorkerError {
|
|||||||
InvaildState,
|
InvaildState,
|
||||||
#[error("OPFS already opened")]
|
#[error("OPFS already opened")]
|
||||||
OpfsSAHPoolOpened,
|
OpfsSAHPoolOpened,
|
||||||
#[error("OPFS unexpected error")]
|
#[error("Failed to import db: {0}")]
|
||||||
OpfsSAHError,
|
LoadDb(String),
|
||||||
|
#[error("Unexpected error")]
|
||||||
|
Unexpected,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@@ -92,6 +97,7 @@ pub enum WorkerRequest {
|
|||||||
StepOver(String),
|
StepOver(String),
|
||||||
StepIn(String),
|
StepIn(String),
|
||||||
StepOut(String),
|
StepOut(String),
|
||||||
|
LoadDb(LoadDbOptions),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@@ -103,6 +109,7 @@ pub enum WorkerResponse {
|
|||||||
StepOver(Result<SQLiteStatementResult>),
|
StepOver(Result<SQLiteStatementResult>),
|
||||||
StepIn(Result<()>),
|
StepIn(Result<()>),
|
||||||
StepOut(Result<SQLiteStatementResult>),
|
StepOut(Result<SQLiteStatementResult>),
|
||||||
|
LoadDb(Result<()>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@@ -111,6 +118,14 @@ pub struct OpenOptions {
|
|||||||
pub persist: bool,
|
pub persist: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct LoadDbOptions {
|
||||||
|
pub id: String,
|
||||||
|
pub persist: bool,
|
||||||
|
#[serde(with = "serde_wasm_bindgen::preserve")]
|
||||||
|
pub data: Uint8Array,
|
||||||
|
}
|
||||||
|
|
||||||
impl OpenOptions {
|
impl OpenOptions {
|
||||||
pub fn uri(&self) -> String {
|
pub fn uri(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
@@ -158,11 +173,11 @@ pub struct SQLiteStatementValues {
|
|||||||
pub enum SQLitendError {
|
pub enum SQLitendError {
|
||||||
#[error("An error occurred while converting a string to a CString")]
|
#[error("An error occurred while converting a string to a CString")]
|
||||||
ToCStr,
|
ToCStr,
|
||||||
#[error("An error occurred while opening the DB: {0:?}")]
|
#[error("An error occurred while opening the DB: {0:#?}")]
|
||||||
OpenDb(InnerError),
|
OpenDb(InnerError),
|
||||||
#[error("An error occurred while preparing stmt: {0:?}")]
|
#[error("An error occurred while preparing stmt: {0:#?}")]
|
||||||
Prepare(InnerError),
|
Prepare(InnerError),
|
||||||
#[error("An error occurred while stepping to the next line: {0:?}")]
|
#[error("An error occurred while stepping to the next line: {0:#?}")]
|
||||||
Step(InnerError),
|
Step(InnerError),
|
||||||
#[error("An error occurred while getting column name: {0}")]
|
#[error("An error occurred while getting column name: {0}")]
|
||||||
GetColumnName(String),
|
GetColumnName(String),
|
||||||
@@ -239,6 +254,15 @@ pub async fn handle_state(state: Store<GlobalState>, mut rx: UnboundedReceiver<W
|
|||||||
WorkerResponse::StepOver(_)
|
WorkerResponse::StepOver(_)
|
||||||
| WorkerResponse::StepIn(_)
|
| WorkerResponse::StepIn(_)
|
||||||
| WorkerResponse::StepOut(_) => unimplemented!(),
|
| WorkerResponse::StepOut(_) => unimplemented!(),
|
||||||
|
WorkerResponse::LoadDb(result) => {
|
||||||
|
let keep_ctx = result.is_ok();
|
||||||
|
if let Err(err) = result {
|
||||||
|
state.last_error().set(Some(SQLightError::new_worker(err)));
|
||||||
|
}
|
||||||
|
state
|
||||||
|
.keep_ctx()
|
||||||
|
.maybe_update(|keep| std::mem::replace(keep, keep_ctx) != keep_ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
mod sqlitend;
|
mod sqlitend;
|
||||||
|
|
||||||
use crate::{OpenOptions, PERSIST_VFS, PrepareOptions, SQLiteStatementResult, WorkerError};
|
use crate::{
|
||||||
|
LoadDbOptions, OpenOptions, PERSIST_VFS, PrepareOptions, SQLiteStatementResult, WorkerError,
|
||||||
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use sqlite_wasm_rs::{
|
use sqlite_wasm_rs::{
|
||||||
export::{OpfsSAHPoolCfgBuilder, OpfsSAHPoolUtil},
|
export::{OpfsSAHPoolCfgBuilder, OpfsSAHPoolUtil},
|
||||||
mem_vfs::MemVfsUtil,
|
mem_vfs::MemVfsUtil,
|
||||||
|
utils::copy_to_vec,
|
||||||
};
|
};
|
||||||
use sqlitend::{SQLiteDb, SQLitePreparedStatement, SQLiteStatements};
|
use sqlitend::{SQLiteDb, SQLitePreparedStatement, SQLiteStatements};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
@@ -50,12 +53,8 @@ where
|
|||||||
f(DB_POOL.lock().get_mut(id).ok_or(WorkerError::NotFound)?)
|
f(DB_POOL.lock().get_mut(id).ok_or(WorkerError::NotFound)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn open(options: OpenOptions) -> Result<String> {
|
async fn opfs_util() -> Result<&'static OpfsSAHPoolUtil> {
|
||||||
if let Some(worker) = DB_POOL.lock().get(&options.filename) {
|
FS_UTIL
|
||||||
return Ok(worker.id.clone());
|
|
||||||
}
|
|
||||||
if options.persist {
|
|
||||||
let util = FS_UTIL
|
|
||||||
.opfs
|
.opfs
|
||||||
.get_or_try_init(|| async {
|
.get_or_try_init(|| async {
|
||||||
sqlite_wasm_rs::sahpool_vfs::install(
|
sqlite_wasm_rs::sahpool_vfs::install(
|
||||||
@@ -70,11 +69,47 @@ pub async fn open(options: OpenOptions) -> Result<String> {
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| WorkerError::OpfsSAHPoolOpened)
|
.map_err(|_| WorkerError::OpfsSAHPoolOpened)
|
||||||
})
|
})
|
||||||
.await?;
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load_db(options: LoadDbOptions) -> Result<()> {
|
||||||
|
let db = copy_to_vec(&options.data);
|
||||||
|
with_worker(&options.id, |worker| {
|
||||||
|
worker.db.take();
|
||||||
|
|
||||||
|
let filename = &worker.open_options.filename;
|
||||||
|
let FSUtil { mem, opfs } = &*FS_UTIL;
|
||||||
|
if worker.open_options.persist {
|
||||||
|
if let Some(opfs) = opfs.get() {
|
||||||
|
opfs.unlink(filename).map_err(|_| WorkerError::Unexpected)?;
|
||||||
|
|
||||||
|
if let Err(err) = opfs.import_db(filename, &db) {
|
||||||
|
return Err(WorkerError::LoadDb(format!("{err}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mem.delete_db(filename);
|
||||||
|
if let Err(err) = mem.import_db(filename, &db) {
|
||||||
|
return Err(WorkerError::LoadDb(format!("{err}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.db = Some(SQLiteDb::open(&worker.open_options.uri())?);
|
||||||
|
worker.state = SQLiteState::Idie;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn open(options: OpenOptions) -> Result<String> {
|
||||||
|
if let Some(worker) = DB_POOL.lock().get(&options.filename) {
|
||||||
|
return Ok(worker.id.clone());
|
||||||
|
}
|
||||||
|
if options.persist {
|
||||||
|
let util = opfs_util().await?;
|
||||||
if util.get_capacity() - util.get_file_count() * 3 < 3 {
|
if util.get_capacity() - util.get_file_count() * 3 < 3 {
|
||||||
util.add_capacity(3)
|
util.add_capacity(3)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| WorkerError::OpfsSAHError)?;
|
.map_err(|_| WorkerError::Unexpected)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// FIXME: multi db support
|
// FIXME: multi db support
|
||||||
@@ -99,8 +134,7 @@ pub fn prepare(options: PrepareOptions) -> Result<()> {
|
|||||||
let FSUtil { mem, opfs } = &*FS_UTIL;
|
let FSUtil { mem, opfs } = &*FS_UTIL;
|
||||||
if worker.open_options.persist {
|
if worker.open_options.persist {
|
||||||
if let Some(opfs) = opfs.get() {
|
if let Some(opfs) = opfs.get() {
|
||||||
opfs.unlink(filename)
|
opfs.unlink(filename).map_err(|_| WorkerError::Unexpected)?;
|
||||||
.map_err(|_| WorkerError::OpfsSAHError)?;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mem.delete_db(filename);
|
mem.delete_db(filename);
|
||||||
|
|||||||
Reference in New Issue
Block a user