2020-08-05 22:08:55 +05:30
|
|
|
/*
|
|
|
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2019-06-27 01:30:50 +05:30
|
|
|
import { StorageError } from "../common.js";
|
|
|
|
|
2019-07-01 13:30:29 +05:30
|
|
|
|
|
|
|
// storage keys are defined to be unsigned 32bit numbers in WebPlatform.js, which is assumed by idb
|
|
|
|
export function encodeUint32(n) {
|
|
|
|
const hex = n.toString(16);
|
|
|
|
return "0".repeat(8 - hex.length) + hex;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function decodeUint32(str) {
|
|
|
|
return parseInt(str, 16);
|
|
|
|
}
|
|
|
|
|
2019-02-07 04:49:14 +05:30
|
|
|
export function openDatabase(name, createObjectStore, version) {
|
2019-02-07 03:36:56 +05:30
|
|
|
const req = window.indexedDB.open(name, version);
|
|
|
|
req.onupgradeneeded = (ev) => {
|
|
|
|
const db = ev.target.result;
|
|
|
|
const oldVersion = ev.oldVersion;
|
|
|
|
createObjectStore(db, oldVersion, version);
|
|
|
|
};
|
|
|
|
return reqAsPromise(req);
|
2019-01-09 15:36:09 +05:30
|
|
|
}
|
|
|
|
|
2019-06-27 01:30:50 +05:30
|
|
|
function wrapError(err) {
|
|
|
|
return new StorageError(`wrapped DOMException`, err);
|
|
|
|
}
|
|
|
|
|
2019-01-09 15:36:09 +05:30
|
|
|
export function reqAsPromise(req) {
|
|
|
|
return new Promise((resolve, reject) => {
|
2019-02-07 03:36:33 +05:30
|
|
|
req.addEventListener("success", event => resolve(event.target.result));
|
2019-06-27 01:30:50 +05:30
|
|
|
req.addEventListener("error", event => reject(wrapError(event.target.error)));
|
2019-01-09 15:36:09 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function txnAsPromise(txn) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
txn.addEventListener("complete", resolve);
|
2019-11-21 22:53:48 +05:30
|
|
|
txn.addEventListener("abort", event => reject(wrapError(event.target.error)));
|
2019-01-09 15:36:09 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-09-15 15:53:54 +05:30
|
|
|
export function iterateCursor(cursorRequest, processValue) {
|
2019-02-07 03:36:56 +05:30
|
|
|
// TODO: does cursor already have a value here??
|
2019-01-09 15:36:09 +05:30
|
|
|
return new Promise((resolve, reject) => {
|
2019-09-15 15:53:54 +05:30
|
|
|
cursorRequest.onerror = () => {
|
|
|
|
reject(new StorageError("Query failed", cursorRequest.error));
|
2019-01-09 15:36:09 +05:30
|
|
|
};
|
|
|
|
// collect results
|
2019-09-15 15:53:54 +05:30
|
|
|
cursorRequest.onsuccess = (event) => {
|
2019-01-09 15:36:09 +05:30
|
|
|
const cursor = event.target.result;
|
|
|
|
if (!cursor) {
|
|
|
|
resolve(false);
|
|
|
|
return; // end of results
|
|
|
|
}
|
2019-05-11 16:40:31 +05:30
|
|
|
const {done, jumpTo} = processValue(cursor.value, cursor.key);
|
|
|
|
if (done) {
|
2019-02-07 03:36:56 +05:30
|
|
|
resolve(true);
|
2019-06-27 01:32:00 +05:30
|
|
|
} else if(jumpTo) {
|
2019-05-11 16:40:31 +05:30
|
|
|
cursor.continue(jumpTo);
|
2019-06-27 01:32:00 +05:30
|
|
|
} else {
|
|
|
|
cursor.continue();
|
2019-01-09 15:36:09 +05:30
|
|
|
}
|
|
|
|
};
|
2019-06-27 01:30:50 +05:30
|
|
|
}).catch(err => {
|
|
|
|
throw new StorageError("iterateCursor failed", err);
|
2019-01-09 15:36:09 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function fetchResults(cursor, isDone) {
|
|
|
|
const results = [];
|
|
|
|
await iterateCursor(cursor, (value) => {
|
2019-02-07 03:36:56 +05:30
|
|
|
results.push(value);
|
2019-05-11 16:40:31 +05:30
|
|
|
return {done: isDone(results)};
|
2019-01-09 15:36:09 +05:30
|
|
|
});
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function select(db, storeName, toCursor, isDone) {
|
2019-02-07 03:36:56 +05:30
|
|
|
if (!isDone) {
|
|
|
|
isDone = () => false;
|
|
|
|
}
|
|
|
|
if (!toCursor) {
|
|
|
|
toCursor = store => store.openCursor();
|
|
|
|
}
|
|
|
|
const tx = db.transaction([storeName], "readonly");
|
|
|
|
const store = tx.objectStore(storeName);
|
|
|
|
const cursor = toCursor(store);
|
|
|
|
return await fetchResults(cursor, isDone);
|
2019-01-09 15:36:09 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
export async function updateSingletonStore(db, storeName, value) {
|
2019-02-07 03:36:56 +05:30
|
|
|
const tx = db.transaction([storeName], "readwrite");
|
|
|
|
const store = tx.objectStore(storeName);
|
|
|
|
const cursor = await reqAsPromise(store.openCursor());
|
|
|
|
if (cursor) {
|
|
|
|
return reqAsPromise(cursor.update(storeName));
|
|
|
|
} else {
|
|
|
|
return reqAsPromise(store.add(value));
|
|
|
|
}
|
2019-01-09 15:36:09 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
export async function findStoreValue(db, storeName, toCursor, matchesValue) {
|
2019-02-07 03:36:56 +05:30
|
|
|
if (!matchesValue) {
|
|
|
|
matchesValue = () => true;
|
|
|
|
}
|
|
|
|
if (!toCursor) {
|
|
|
|
toCursor = store => store.openCursor();
|
|
|
|
}
|
2019-01-09 15:36:09 +05:30
|
|
|
|
2019-02-07 03:36:56 +05:30
|
|
|
const tx = db.transaction([storeName], "readwrite");
|
|
|
|
const store = tx.objectStore(storeName);
|
|
|
|
const cursor = await reqAsPromise(toCursor(store));
|
|
|
|
let match;
|
|
|
|
const matched = await iterateCursor(cursor, (value) => {
|
|
|
|
if (matchesValue(value)) {
|
|
|
|
match = value;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (!matched) {
|
2019-06-27 01:30:50 +05:30
|
|
|
throw new StorageError("Value not found");
|
2019-02-07 03:36:56 +05:30
|
|
|
}
|
|
|
|
return match;
|
2019-05-11 16:40:31 +05:30
|
|
|
}
|