diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/fiddle/fiddle-worker.js | 77 | ||||
-rw-r--r-- | ext/fiddle/fiddle.html | 15 | ||||
-rw-r--r-- | ext/fiddle/fiddle.js | 43 | ||||
-rw-r--r-- | ext/fiddle/sqlite3-api.js | 34 |
4 files changed, 143 insertions, 26 deletions
diff --git a/ext/fiddle/fiddle-worker.js b/ext/fiddle/fiddle-worker.js index 16da63606..6a687e42c 100644 --- a/ext/fiddle/fiddle-worker.js +++ b/ext/fiddle/fiddle-worker.js @@ -159,30 +159,73 @@ self.Module = { } }; -const shellExec = function f(sql){ - if(!f._) f._ = Module.cwrap('fiddle_exec', null, ['string']); - if(Module._isDead){ - wMsg('stderr', "shell module has exit()ed. Cannot run SQL."); - return; - } - wMsg('working','start'); - try { - if(f._running) wMsg('stderr','Cannot run multiple commands concurrently.'); - else{ - f._running = true; - f._(sql); +const Sqlite3Shell = { + exec: function f(sql){ + if(!f._) f._ = Module.cwrap('fiddle_exec', null, ['string']); + if(Module._isDead){ + wMsg('stderr', "shell module has exit()ed. Cannot run SQL."); + return; + } + wMsg('working','start'); + try { + if(f._running) wMsg('stderr','Cannot run multiple commands concurrently.'); + else{ + f._running = true; + f._(sql); + } + } finally { + wMsg('working','end'); + delete f._running; } - } finally { - wMsg('working','end'); - delete f._running; + }, + /* Interrupt can't work: this Worker is tied up working, so won't get the + interrupt event which would be needed to perform the interrupt. */ + interrupt: function f(){ + if(!f._) f._ = Module.cwrap('fiddle_interrupt', null); + wMsg('stdout',"Requesting interrupt."); + f._(); } }; -self.onmessage = function(ev){ +self.onmessage = function f(ev){ ev = ev.data; + if(!f.cache){ + f.cache = { + prevFilename: null + }; + } //console.debug("worker: onmessage.data",ev); switch(ev.type){ - case 'shellExec': shellExec(ev.data); return; + case 'shellExec': Sqlite3Shell.exec(ev.data); return; + case 'interrupt': Sqlite3Shell.interrupt(); return; + case 'open': { + /* Expects: { + buffer: ArrayBuffer | Uint8Array, + filename: for logging/informational purposes only + } */ + const opt = ev.data; + let buffer = opt.buffer; + if(buffer instanceof Uint8Array){ + }else if(buffer instanceof ArrayBuffer){ + buffer = new Uint8Array(buffer); + }else{ + wMsg('stderr',"'open' expects {buffer:Uint8Array} containing an uploaded db."); + return; + } + if(f.cache.prevFilename){ + FS.unlink(f.cache.prevFilename); + /* Noting that it might not actually be removed until + the current db handle closes it. */ + f.cache.prevFilename = null; + } + const fn = "db-"+((Math.random() * 10000000) | 0)+ + "-"+((Math.random() * 10000000) | 0)+".sqlite3"; + FS.createDataFile("/", fn, buffer, true, true); + f.cache.prevFilename = fn; + Sqlite3Shell.exec(".open /"+fn); + wMsg('stdout',"Replaced DB with "+(opt.filename || fn)+"."); + return; + } }; console.warn("Unknown fiddle-worker message type:",ev); }; diff --git a/ext/fiddle/fiddle.html b/ext/fiddle/fiddle.html index 52ae2467e..8db780e4d 100644 --- a/ext/fiddle/fiddle.html +++ b/ext/fiddle/fiddle.html @@ -76,6 +76,10 @@ fieldset > legend { padding: 0 0.5em; } + fieldset.options > div { + display: flex; + flex-wrap: wrap; + } span.labeled-input { padding: 0.25em; margin: 0.25em 0.5em; @@ -108,7 +112,7 @@ } #view-split { display: flex; - flex-direction: column; + flex-direction: column-reverse; } </style> </head> @@ -161,6 +165,10 @@ data-config='autoClearOutput'> <label for='opt-cb-autoclear'>Auto-clear output</label> </span> + <span class='labeled-input'> + <input type='file' id='load-db'/> + <label>Load DB</label> + </span> </div> </fieldset> <div id='main-wrapper' class=''> @@ -185,6 +193,11 @@ SELECT * FROM t;</textarea> placeholder="Shell output."></textarea> <div class='button-bar'> <button id='btn-clear-output'>Clear Output</button> + <button id='btn-interrupt' class='hidden' disabled>Interrupt</button> + <!-- interruption cannot work in the current configuration + because we cannot send an interrupt message when work + is currently underway. At that point the Worker is + tied up and will not receive the message. */ </div> </div> </div> diff --git a/ext/fiddle/fiddle.js b/ext/fiddle/fiddle.js index 5590279d0..796c6bced 100644 --- a/ext/fiddle/fiddle.js +++ b/ext/fiddle/fiddle.js @@ -218,6 +218,8 @@ if(sql) SF.dbExec(sql); },false); + const btnInterrupt = E("#btn-interrupt"); + btnInterrupt.classList.add('hidden'); /** To be called immediately before work is sent to the worker. Updates some UI elements. The 'working'/'end' event will apply the inverse, undoing the bits this @@ -237,6 +239,7 @@ } f._.pageTitle.innerText = "[working...] "+f._.pageTitleOrig; btnShellExec.setAttribute('disabled','disabled'); + btnInterrupt.removeAttribute('disabled','disabled'); }; /* Sends the given text to the db module to evaluate as if it @@ -258,6 +261,7 @@ preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig; btnShellExec.innerText = preStartWork._.btnLabel; btnShellExec.removeAttribute('disabled'); + btnInterrupt.setAttribute('disabled','disabled'); return; } console.warn("Unhandled 'working' event:",ev.data); @@ -294,12 +298,47 @@ }, false); }); /* For each button with data-cmd=X, map a click handler which - calls dbExec(X). */ + calls SF.dbExec(X). */ const cmdClick = function(){SF.dbExec(this.dataset.cmd);}; EAll('button[data-cmd]').forEach( e => e.addEventListener('click', cmdClick, false) ); + btnInterrupt.addEventListener('click',function(){ + SF.wMsg('interrupt'); + }); + + const fileSelector = E('#load-db'); + fileSelector.addEventListener('change',function(){ + const f = this.files[0]; + const r = new FileReader(); + const status = {loaded: 0, total: 0}; + fileSelector.setAttribute('disabled','disabled'); + r.addEventListener('loadstart', function(){ + SF.echo("Loading",f.name,"..."); + }); + r.addEventListener('progress', function(ev){ + SF.echo("Loading progress:",ev.loaded,"of",ev.total,"bytes."); + }); + r.addEventListener('load', function(){ + fileSelector.removeAttribute('disabled'); + SF.echo("Loaded",f.name+". Opening db..."); + SF.wMsg('open',{ + filename: f.name, + buffer: this.result + }); + }); + r.addEventListener('error',function(){ + fileSelector.removeAttribute('disabled'); + SF.echo("Loading",f.name,"failed for unknown reason."); + }); + r.addEventListener('abort',function(){ + fileSelector.removeAttribute('disabled'); + SF.echo("Cancelled loading of",f.name+"."); + }); + r.readAsArrayBuffer(f); + }); + /** Given a DOM element, this routine measures its "effective height", which is the bounding top/bottom range of this element @@ -445,7 +484,7 @@ SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;`} taInput.value = '-- ' + this.selectedOptions[0].innerText + '\n' + this.value; - //dbExec(this.value); + SF.dbExec(this.value); }); })()/* example queries */; diff --git a/ext/fiddle/sqlite3-api.js b/ext/fiddle/sqlite3-api.js index e97e7c183..1bf162900 100644 --- a/ext/fiddle/sqlite3-api.js +++ b/ext/fiddle/sqlite3-api.js @@ -253,17 +253,39 @@ }; /** - The DB class wraps a sqlite3 db handle. + The DB class wraps a sqlite3 db handle. Its argument may either + be a db name or a Uint8Array containing a binary image of a + database. If the name is not provided or is an empty string, + ":memory:" is used. A string name other than ":memory:" or "" + will currently fail to open, for lack of a filesystem to + load it from. If given a blob, a random name is generated. + + Achtung: all arguments other than those specifying an + in-memory db are currently untested for lack of an app + to test them in. */ - const DB = function(name/*TODO: openMode flags*/){ - if(!name) name = ':memory:'; - else if('string'!==typeof name){ + const DB = function(name/*TODO? openMode flags*/){ + let fn, buff; + if(name instanceof Uint8Array){ + buff = name; + name = undefined; + fn = "db-"+((Math.random() * 10000000) | 0)+ + "-"+((Math.random() * 10000000) | 0)+".sqlite3"; + }else if(":memory:" === name || "" === name){ + fn = name || ":memory:"; + name = undefined; + }else if('string'!==typeof name){ toss("TODO: support blob image of db here."); + }else{ + fn = name; + } + if(buff){ + FS.createDataFile("/", fn, buff, true, true); } setValue(pPtrArg, 0, "i32"); - this.checkRc(api.sqlite3_open(name, pPtrArg)); + this.checkRc(api.sqlite3_open(fn, pPtrArg)); this._pDb = getValue(pPtrArg, "i32"); - this.filename = name; + this.filename = fn; this._statements = {/*map of open Stmt _pointers_ to Stmt*/}; this._udfs = {/*map of UDF names to wasm function _pointers_*/}; }; |