296 lines
7.9 KiB
JavaScript
296 lines
7.9 KiB
JavaScript
|
/*
|
||
|
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
const platfFilesystemName = "platformFilesystem";
|
||
|
|
||
|
/**
|
||
|
* @param {Object} k
|
||
|
* @return {string}
|
||
|
*/
|
||
|
function readDBKey(k) {
|
||
|
return ((typeof k) === "string") ? k : (((typeof k) === "undefined") ? null : (((typeof k[0]) === "string") ? k[0] : null));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {Object} obj
|
||
|
* @return {ArrayBuffer}
|
||
|
*/
|
||
|
function readDBRow(obj) {
|
||
|
return (typeof obj === "undefined") ? null : ((typeof obj["data"] === "undefined") ? null : obj["data"]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {string} pat
|
||
|
* @param {ArrayBuffer} dat
|
||
|
* @return {Object}
|
||
|
*/
|
||
|
function writeDBRow(pat, dat) {
|
||
|
return { "path": pat, "data": dat };
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {string} filesystemDB
|
||
|
* @return {Promise}
|
||
|
*/
|
||
|
function openDBImpl(filesystemDB) {
|
||
|
return new Promise(function(resolve) {
|
||
|
if(typeof indexedDB === "undefined") {
|
||
|
resolve({
|
||
|
"failedInit": true,
|
||
|
"failedLocked": false,
|
||
|
"failedError": "IndexedDB not supported",
|
||
|
"database": null
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
let dbOpen;
|
||
|
try {
|
||
|
dbOpen = indexedDB.open(filesystemDB, 1);
|
||
|
}catch(err) {
|
||
|
resolve({
|
||
|
"failedInit": true,
|
||
|
"failedLocked": false,
|
||
|
"failedError": "Exception opening database",
|
||
|
"database": null
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
let resultConsumer = resolve;
|
||
|
dbOpen.addEventListener("success", function(evt) {
|
||
|
if(resultConsumer) resultConsumer({
|
||
|
"failedInit": false,
|
||
|
"failedLocked": false,
|
||
|
"failedError": null,
|
||
|
"database": dbOpen.result
|
||
|
});
|
||
|
resultConsumer = null;
|
||
|
});
|
||
|
dbOpen.addEventListener("blocked", function(evt) {
|
||
|
if(resultConsumer) resultConsumer({
|
||
|
"failedInit": false,
|
||
|
"failedLocked": true,
|
||
|
"failedError": "Database is locked",
|
||
|
"database": null
|
||
|
});
|
||
|
resultConsumer = null;
|
||
|
});
|
||
|
dbOpen.addEventListener("error", function(evt) {
|
||
|
if(resultConsumer) resultConsumer({
|
||
|
"failedInit": true,
|
||
|
"failedLocked": false,
|
||
|
"failedError": "Opening database failed",
|
||
|
"database": null
|
||
|
});
|
||
|
resultConsumer = null;
|
||
|
});
|
||
|
dbOpen.addEventListener("upgradeneeded", function(evt) {
|
||
|
dbOpen.result.createObjectStore("filesystem", { keyPath: ["path"] });
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
eagruntimeImpl.platformFilesystem["openDB"] = new WebAssembly.Suspending(openDBImpl);
|
||
|
|
||
|
/**
|
||
|
* @param {IDBDatabase} database
|
||
|
* @param {string} pathName
|
||
|
* @return {Promise}
|
||
|
*/
|
||
|
function eaglerDeleteImpl(database, pathName) {
|
||
|
return new Promise(function(resolve) {
|
||
|
const tx = database.transaction("filesystem", "readwrite");
|
||
|
const r = tx.objectStore("filesystem").delete([pathName]);
|
||
|
r.addEventListener("success", function() {
|
||
|
resolve(true);
|
||
|
});
|
||
|
r.addEventListener("error", function() {
|
||
|
resolve(false);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
eagruntimeImpl.platformFilesystem["eaglerDelete"] = new WebAssembly.Suspending(eaglerDeleteImpl);
|
||
|
|
||
|
/**
|
||
|
* @param {IDBDatabase} database
|
||
|
* @param {string} pathName
|
||
|
* @return {Promise}
|
||
|
*/
|
||
|
function eaglerReadImpl(database, pathName) {
|
||
|
return new Promise(function(resolve) {
|
||
|
const tx = database.transaction("filesystem", "readonly");
|
||
|
const r = tx.objectStore("filesystem").get([pathName]);
|
||
|
r.addEventListener("success", function() {
|
||
|
resolve(readDBRow(r.result));
|
||
|
});
|
||
|
r.addEventListener("error", function() {
|
||
|
resolve(null);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
eagruntimeImpl.platformFilesystem["eaglerRead"] = new WebAssembly.Suspending(eaglerReadImpl);
|
||
|
|
||
|
/**
|
||
|
* @param {IDBDatabase} database
|
||
|
* @param {string} pathName
|
||
|
* @param {ArrayBuffer} arr
|
||
|
* @return {Promise}
|
||
|
*/
|
||
|
function eaglerWriteImpl(database, pathName, arr) {
|
||
|
return new Promise(function(resolve) {
|
||
|
const tx = database.transaction("filesystem", "readwrite");
|
||
|
const r = tx.objectStore("filesystem").put(writeDBRow(pathName, arr));
|
||
|
r.addEventListener("success", function() {
|
||
|
resolve(true);
|
||
|
});
|
||
|
r.addEventListener("error", function() {
|
||
|
resolve(false);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
eagruntimeImpl.platformFilesystem["eaglerWrite"] = new WebAssembly.Suspending(eaglerWriteImpl);
|
||
|
|
||
|
/**
|
||
|
* @param {IDBDatabase} database
|
||
|
* @param {string} pathName
|
||
|
* @return {Promise}
|
||
|
*/
|
||
|
function eaglerExistsImpl(database, pathName) {
|
||
|
return new Promise(function(resolve) {
|
||
|
const tx = database.transaction("filesystem", "readonly");
|
||
|
const r = tx.objectStore("filesystem").count([pathName]);
|
||
|
r.addEventListener("success", function() {
|
||
|
resolve(r.result > 0);
|
||
|
});
|
||
|
r.addEventListener("error", function() {
|
||
|
resolve(false);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
eagruntimeImpl.platformFilesystem["eaglerExists"] = new WebAssembly.Suspending(eaglerExistsImpl);
|
||
|
|
||
|
/**
|
||
|
* @param {IDBDatabase} database
|
||
|
* @param {string} pathNameOld
|
||
|
* @param {string} pathNameNew
|
||
|
* @return {Promise<boolean>}
|
||
|
*/
|
||
|
async function eaglerMoveImpl(database, pathNameOld, pathNameNew) {
|
||
|
const oldData = await eaglerReadImpl(database, pathNameOld);
|
||
|
if(!oldData || !(await eaglerWriteImpl(database, pathNameNew, oldData))) {
|
||
|
return false;
|
||
|
}
|
||
|
return await eaglerDeleteImpl(database, pathNameOld);
|
||
|
}
|
||
|
|
||
|
eagruntimeImpl.platformFilesystem["eaglerMove"] = new WebAssembly.Suspending(eaglerMoveImpl);
|
||
|
|
||
|
/**
|
||
|
* @param {IDBDatabase} database
|
||
|
* @param {string} pathNameOld
|
||
|
* @param {string} pathNameNew
|
||
|
* @return {Promise<boolean>}
|
||
|
*/
|
||
|
async function eaglerCopyImpl(database, pathNameOld, pathNameNew) {
|
||
|
const oldData = await eaglerReadImpl(database, pathNameOld);
|
||
|
return oldData && (await eaglerWriteImpl(database, pathNameNew, oldData));
|
||
|
}
|
||
|
|
||
|
eagruntimeImpl.platformFilesystem["eaglerCopy"] = new WebAssembly.Suspending(eaglerCopyImpl);
|
||
|
|
||
|
/**
|
||
|
* @param {IDBDatabase} database
|
||
|
* @param {string} pathName
|
||
|
* @return {Promise}
|
||
|
*/
|
||
|
function eaglerSizeImpl(database, pathName) {
|
||
|
return new Promise(function(resolve) {
|
||
|
const tx = database.transaction("filesystem", "readonly");
|
||
|
const r = tx.objectStore("filesystem").get([pathName]);
|
||
|
r.addEventListener("success", function() {
|
||
|
const data = readDBRow(r.result);
|
||
|
resolve(data ? data.byteLength : -1);
|
||
|
});
|
||
|
r.addEventListener("error", function() {
|
||
|
resolve(-1);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
eagruntimeImpl.platformFilesystem["eaglerSize"] = new WebAssembly.Suspending(eaglerSizeImpl);
|
||
|
|
||
|
/**
|
||
|
* @param {string} str
|
||
|
* @return {number}
|
||
|
*/
|
||
|
function countSlashes(str) {
|
||
|
if(str.length === 0) return -1;
|
||
|
var j = 0;
|
||
|
for(var i = 0, l = str.length; i < l; ++i) {
|
||
|
if(str.charCodeAt(i) === 47) {
|
||
|
++j;
|
||
|
}
|
||
|
}
|
||
|
return j;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {IDBDatabase} database
|
||
|
* @param {string} pathName
|
||
|
* @param {number} recursive
|
||
|
* @return {Promise}
|
||
|
*/
|
||
|
function eaglerIterateImpl(database, pathName, recursive) {
|
||
|
return new Promise(function(resolve) {
|
||
|
const rows = [];
|
||
|
const tx = database.transaction("filesystem", "readonly");
|
||
|
const r = tx.objectStore("filesystem").openCursor();
|
||
|
const b = pathName.length === 0;
|
||
|
const pc = recursive ? -1 : countSlashes(pathName);
|
||
|
r.addEventListener("success", function() {
|
||
|
const c = r.result;
|
||
|
if(c === null || c.key === null) {
|
||
|
resolve({
|
||
|
"length": rows.length,
|
||
|
/**
|
||
|
* @param {number} idx
|
||
|
* @return {string}
|
||
|
*/
|
||
|
"getRow": function(idx) {
|
||
|
return rows[idx];
|
||
|
}
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
const k = readDBKey(c.key);
|
||
|
if(k != null) {
|
||
|
if((b || k.startsWith(pathName)) && (recursive || countSlashes(k) === pc)) {
|
||
|
rows.push(k);
|
||
|
}
|
||
|
}
|
||
|
c.continue();
|
||
|
});
|
||
|
r.addEventListener("error", function() {
|
||
|
resolve(null);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
eagruntimeImpl.platformFilesystem["eaglerIterate"] = new WebAssembly.Suspending(eaglerIterateImpl);
|