From a16a8f53e657596eece7c3eb7c21da4ffc2a0fe7 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 28 Jun 2020 17:29:16 -0400 Subject: Rename GenericDatabase to Database (#634) --- ext/bg/background.html | 2 +- ext/bg/js/database.js | 327 +++++++++++++++++++++++++++++++++++++++ ext/bg/js/dictionary-database.js | 6 +- ext/bg/js/generic-database.js | 327 --------------------------------------- 4 files changed, 331 insertions(+), 331 deletions(-) create mode 100644 ext/bg/js/database.js delete mode 100644 ext/bg/js/generic-database.js (limited to 'ext') diff --git a/ext/bg/background.html b/ext/bg/background.html index 0591032d..a30b55a5 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -31,7 +31,7 @@ - + diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js new file mode 100644 index 00000000..96eaa340 --- /dev/null +++ b/ext/bg/js/database.js @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2020 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +class Database { + constructor() { + this._db = null; + this._isOpening = false; + } + + // Public + + async open(databaseName, version, structure) { + if (this._db !== null) { + throw new Error('Database already open'); + } + if (this._isOpening) { + throw new Error('Already opening'); + } + + try { + this._isOpening = true; + this._db = await this._open(databaseName, version, (db, transaction, oldVersion) => { + this._upgrade(db, transaction, oldVersion, structure); + }); + } finally { + this._isOpening = false; + } + } + + close() { + if (this._db === null) { + throw new Error('Database is not open'); + } + + this._db.close(); + this._db = null; + } + + isOpening() { + return this._isOpening; + } + + isOpen() { + return this._db !== null; + } + + transaction(storeNames, mode) { + if (this._db === null) { + throw new Error(this._isOpening ? 'Database not ready' : 'Database not open'); + } + return this._db.transaction(storeNames, mode); + } + + bulkAdd(objectStoreName, items, start, count) { + return new Promise((resolve, reject) => { + if (start + count > items.length) { + count = items.length - start; + } + + if (count <= 0) { + resolve(); + return; + } + + const end = start + count; + let completedCount = 0; + const onError = (e) => reject(e.target.error); + const onSuccess = () => { + if (++completedCount >= count) { + resolve(); + } + }; + + const transaction = this.transaction([objectStoreName], 'readwrite'); + const objectStore = transaction.objectStore(objectStoreName); + for (let i = start; i < end; ++i) { + const request = objectStore.add(items[i]); + request.onerror = onError; + request.onsuccess = onSuccess; + } + }); + } + + getAll(objectStoreOrIndex, query, resolve, reject) { + if (typeof objectStoreOrIndex.getAll === 'function') { + this._getAllFast(objectStoreOrIndex, query, resolve, reject); + } else { + this._getAllUsingCursor(objectStoreOrIndex, query, resolve, reject); + } + } + + getAllKeys(objectStoreOrIndex, query, resolve, reject) { + if (typeof objectStoreOrIndex.getAll === 'function') { + this._getAllKeysFast(objectStoreOrIndex, query, resolve, reject); + } else { + this._getAllKeysUsingCursor(objectStoreOrIndex, query, resolve, reject); + } + } + + find(objectStoreName, indexName, query, predicate=null, defaultValue) { + return new Promise((resolve, reject) => { + const transaction = this.transaction([objectStoreName], 'readonly'); + const objectStore = transaction.objectStore(objectStoreName); + const objectStoreOrIndex = indexName !== null ? objectStore.index(indexName) : objectStore; + const request = objectStoreOrIndex.openCursor(query, 'next'); + request.onerror = (e) => reject(e.target.error); + request.onsuccess = (e) => { + const cursor = e.target.result; + if (cursor) { + const value = cursor.value; + if (typeof predicate !== 'function' || predicate(value)) { + resolve(value); + } else { + cursor.continue(); + } + } else { + resolve(defaultValue); + } + }; + }); + } + + bulkCount(targets, resolve, reject) { + const targetCount = targets.length; + if (targetCount <= 0) { + resolve(); + return; + } + + let completedCount = 0; + const results = new Array(targetCount).fill(null); + + const onError = (e) => reject(e.target.error); + const onSuccess = (e, index) => { + const count = e.target.result; + results[index] = count; + if (++completedCount >= targetCount) { + resolve(results); + } + }; + + for (let i = 0; i < targetCount; ++i) { + const index = i; + const [objectStoreOrIndex, query] = targets[i]; + const request = objectStoreOrIndex.count(query); + request.onerror = onError; + request.onsuccess = (e) => onSuccess(e, index); + } + } + + delete(objectStoreName, key) { + return new Promise((resolve, reject) => { + const transaction = this.transaction([objectStoreName], 'readwrite'); + const objectStore = transaction.objectStore(objectStoreName); + const request = objectStore.delete(key); + request.onerror = (e) => reject(e.target.error); + request.onsuccess = () => resolve(); + }); + } + + bulkDelete(objectStoreName, indexName, query, filterKeys=null, onProgress=null) { + return new Promise((resolve, reject) => { + const transaction = this.transaction([objectStoreName], 'readwrite'); + const objectStore = transaction.objectStore(objectStoreName); + const objectStoreOrIndex = indexName !== null ? objectStore.index(indexName) : objectStore; + + const onGetKeys = (keys) => { + try { + if (typeof filterKeys === 'function') { + keys = filterKeys(keys); + } + this._bulkDeleteInternal(objectStore, keys, onProgress, resolve, reject); + } catch (e) { + reject(e); + } + }; + + this.getAllKeys(objectStoreOrIndex, query, onGetKeys, reject); + }); + } + + static deleteDatabase(databaseName) { + return new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(databaseName); + request.onerror = (e) => reject(e.target.error); + request.onsuccess = () => resolve(); + request.onblocked = () => reject(new Error('Database deletion blocked')); + }); + } + + // Private + + _open(name, version, onUpgradeNeeded) { + return new Promise((resolve, reject) => { + const request = indexedDB.open(name, version); + + request.onupgradeneeded = (event) => { + try { + request.transaction.onerror = (e) => reject(e.target.error); + onUpgradeNeeded(request.result, request.transaction, event.oldVersion, event.newVersion); + } catch (e) { + reject(e); + } + }; + + request.onerror = (e) => reject(e.target.error); + request.onsuccess = () => resolve(request.result); + }); + } + + _upgrade(db, transaction, oldVersion, upgrades) { + for (const {version, stores} of upgrades) { + if (oldVersion >= version) { continue; } + + for (const [objectStoreName, {primaryKey, indices}] of Object.entries(stores)) { + const existingObjectStoreNames = transaction.objectStoreNames || db.objectStoreNames; + const objectStore = ( + this._listContains(existingObjectStoreNames, objectStoreName) ? + transaction.objectStore(objectStoreName) : + db.createObjectStore(objectStoreName, primaryKey) + ); + const existingIndexNames = objectStore.indexNames; + + for (const indexName of indices) { + if (this._listContains(existingIndexNames, indexName)) { continue; } + + objectStore.createIndex(indexName, indexName, {}); + } + } + } + } + + _listContains(list, value) { + for (let i = 0, ii = list.length; i < ii; ++i) { + if (list[i] === value) { return true; } + } + return false; + } + + _getAllFast(objectStoreOrIndex, query, resolve, reject) { + const request = objectStoreOrIndex.getAll(query); + request.onerror = (e) => reject(e.target.error); + request.onsuccess = (e) => resolve(e.target.result); + } + + _getAllUsingCursor(objectStoreOrIndex, query, resolve, reject) { + const results = []; + const request = objectStoreOrIndex.openCursor(query, 'next'); + request.onerror = (e) => reject(e.target.error); + request.onsuccess = (e) => { + const cursor = e.target.result; + if (cursor) { + results.push(cursor.value); + cursor.continue(); + } else { + resolve(results); + } + }; + } + + _getAllKeysFast(objectStoreOrIndex, query, resolve, reject) { + const request = objectStoreOrIndex.getAllKeys(query); + request.onerror = (e) => reject(e.target.error); + request.onsuccess = (e) => resolve(e.target.result); + } + + _getAllKeysUsingCursor(objectStoreOrIndex, query, resolve, reject) { + const results = []; + const request = objectStoreOrIndex.openKeyCursor(query, 'next'); + request.onerror = (e) => reject(e.target.error); + request.onsuccess = (e) => { + const cursor = e.target.result; + if (cursor) { + results.push(cursor.primaryKey); + cursor.continue(); + } else { + resolve(results); + } + }; + } + + _bulkDeleteInternal(objectStore, keys, onProgress, resolve, reject) { + const count = keys.length; + if (count === 0) { + resolve(); + return; + } + + let completedCount = 0; + const hasProgress = (typeof onProgress === 'function'); + + const onError = (e) => reject(e.target.error); + const onSuccess = () => { + ++completedCount; + if (hasProgress) { + try { + onProgress(completedCount, count); + } catch (e) { + // NOP + } + } + if (completedCount >= count) { + resolve(); + } + }; + + for (const key of keys) { + const request = objectStore.delete(key); + request.onerror = onError; + request.onsuccess = onSuccess; + } + } +} diff --git a/ext/bg/js/dictionary-database.js b/ext/bg/js/dictionary-database.js index c48320cd..671be7a8 100644 --- a/ext/bg/js/dictionary-database.js +++ b/ext/bg/js/dictionary-database.js @@ -16,13 +16,13 @@ */ /* global - * GenericDatabase + * Database * dictFieldSplit */ class DictionaryDatabase { constructor() { - this._db = new GenericDatabase(); + this._db = new Database(); this._dbName = 'dict'; this._schemas = new Map(); } @@ -118,7 +118,7 @@ class DictionaryDatabase { if (this._db.isOpen()) { this._db.close(); } - await GenericDatabase.deleteDatabase(this._dbName); + await Database.deleteDatabase(this._dbName); await this.prepare(); } diff --git a/ext/bg/js/generic-database.js b/ext/bg/js/generic-database.js deleted file mode 100644 index a82ad650..00000000 --- a/ext/bg/js/generic-database.js +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (C) 2020 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -class GenericDatabase { - constructor() { - this._db = null; - this._isOpening = false; - } - - // Public - - async open(databaseName, version, structure) { - if (this._db !== null) { - throw new Error('Database already open'); - } - if (this._isOpening) { - throw new Error('Already opening'); - } - - try { - this._isOpening = true; - this._db = await this._open(databaseName, version, (db, transaction, oldVersion) => { - this._upgrade(db, transaction, oldVersion, structure); - }); - } finally { - this._isOpening = false; - } - } - - close() { - if (this._db === null) { - throw new Error('Database is not open'); - } - - this._db.close(); - this._db = null; - } - - isOpening() { - return this._isOpening; - } - - isOpen() { - return this._db !== null; - } - - transaction(storeNames, mode) { - if (this._db === null) { - throw new Error(this._isOpening ? 'Database not ready' : 'Database not open'); - } - return this._db.transaction(storeNames, mode); - } - - bulkAdd(objectStoreName, items, start, count) { - return new Promise((resolve, reject) => { - if (start + count > items.length) { - count = items.length - start; - } - - if (count <= 0) { - resolve(); - return; - } - - const end = start + count; - let completedCount = 0; - const onError = (e) => reject(e.target.error); - const onSuccess = () => { - if (++completedCount >= count) { - resolve(); - } - }; - - const transaction = this.transaction([objectStoreName], 'readwrite'); - const objectStore = transaction.objectStore(objectStoreName); - for (let i = start; i < end; ++i) { - const request = objectStore.add(items[i]); - request.onerror = onError; - request.onsuccess = onSuccess; - } - }); - } - - getAll(objectStoreOrIndex, query, resolve, reject) { - if (typeof objectStoreOrIndex.getAll === 'function') { - this._getAllFast(objectStoreOrIndex, query, resolve, reject); - } else { - this._getAllUsingCursor(objectStoreOrIndex, query, resolve, reject); - } - } - - getAllKeys(objectStoreOrIndex, query, resolve, reject) { - if (typeof objectStoreOrIndex.getAll === 'function') { - this._getAllKeysFast(objectStoreOrIndex, query, resolve, reject); - } else { - this._getAllKeysUsingCursor(objectStoreOrIndex, query, resolve, reject); - } - } - - find(objectStoreName, indexName, query, predicate=null, defaultValue) { - return new Promise((resolve, reject) => { - const transaction = this.transaction([objectStoreName], 'readonly'); - const objectStore = transaction.objectStore(objectStoreName); - const objectStoreOrIndex = indexName !== null ? objectStore.index(indexName) : objectStore; - const request = objectStoreOrIndex.openCursor(query, 'next'); - request.onerror = (e) => reject(e.target.error); - request.onsuccess = (e) => { - const cursor = e.target.result; - if (cursor) { - const value = cursor.value; - if (typeof predicate !== 'function' || predicate(value)) { - resolve(value); - } else { - cursor.continue(); - } - } else { - resolve(defaultValue); - } - }; - }); - } - - bulkCount(targets, resolve, reject) { - const targetCount = targets.length; - if (targetCount <= 0) { - resolve(); - return; - } - - let completedCount = 0; - const results = new Array(targetCount).fill(null); - - const onError = (e) => reject(e.target.error); - const onSuccess = (e, index) => { - const count = e.target.result; - results[index] = count; - if (++completedCount >= targetCount) { - resolve(results); - } - }; - - for (let i = 0; i < targetCount; ++i) { - const index = i; - const [objectStoreOrIndex, query] = targets[i]; - const request = objectStoreOrIndex.count(query); - request.onerror = onError; - request.onsuccess = (e) => onSuccess(e, index); - } - } - - delete(objectStoreName, key) { - return new Promise((resolve, reject) => { - const transaction = this.transaction([objectStoreName], 'readwrite'); - const objectStore = transaction.objectStore(objectStoreName); - const request = objectStore.delete(key); - request.onerror = (e) => reject(e.target.error); - request.onsuccess = () => resolve(); - }); - } - - bulkDelete(objectStoreName, indexName, query, filterKeys=null, onProgress=null) { - return new Promise((resolve, reject) => { - const transaction = this.transaction([objectStoreName], 'readwrite'); - const objectStore = transaction.objectStore(objectStoreName); - const objectStoreOrIndex = indexName !== null ? objectStore.index(indexName) : objectStore; - - const onGetKeys = (keys) => { - try { - if (typeof filterKeys === 'function') { - keys = filterKeys(keys); - } - this._bulkDeleteInternal(objectStore, keys, onProgress, resolve, reject); - } catch (e) { - reject(e); - } - }; - - this.getAllKeys(objectStoreOrIndex, query, onGetKeys, reject); - }); - } - - static deleteDatabase(databaseName) { - return new Promise((resolve, reject) => { - const request = indexedDB.deleteDatabase(databaseName); - request.onerror = (e) => reject(e.target.error); - request.onsuccess = () => resolve(); - request.onblocked = () => reject(new Error('Database deletion blocked')); - }); - } - - // Private - - _open(name, version, onUpgradeNeeded) { - return new Promise((resolve, reject) => { - const request = indexedDB.open(name, version); - - request.onupgradeneeded = (event) => { - try { - request.transaction.onerror = (e) => reject(e.target.error); - onUpgradeNeeded(request.result, request.transaction, event.oldVersion, event.newVersion); - } catch (e) { - reject(e); - } - }; - - request.onerror = (e) => reject(e.target.error); - request.onsuccess = () => resolve(request.result); - }); - } - - _upgrade(db, transaction, oldVersion, upgrades) { - for (const {version, stores} of upgrades) { - if (oldVersion >= version) { continue; } - - for (const [objectStoreName, {primaryKey, indices}] of Object.entries(stores)) { - const existingObjectStoreNames = transaction.objectStoreNames || db.objectStoreNames; - const objectStore = ( - this._listContains(existingObjectStoreNames, objectStoreName) ? - transaction.objectStore(objectStoreName) : - db.createObjectStore(objectStoreName, primaryKey) - ); - const existingIndexNames = objectStore.indexNames; - - for (const indexName of indices) { - if (this._listContains(existingIndexNames, indexName)) { continue; } - - objectStore.createIndex(indexName, indexName, {}); - } - } - } - } - - _listContains(list, value) { - for (let i = 0, ii = list.length; i < ii; ++i) { - if (list[i] === value) { return true; } - } - return false; - } - - _getAllFast(objectStoreOrIndex, query, resolve, reject) { - const request = objectStoreOrIndex.getAll(query); - request.onerror = (e) => reject(e.target.error); - request.onsuccess = (e) => resolve(e.target.result); - } - - _getAllUsingCursor(objectStoreOrIndex, query, resolve, reject) { - const results = []; - const request = objectStoreOrIndex.openCursor(query, 'next'); - request.onerror = (e) => reject(e.target.error); - request.onsuccess = (e) => { - const cursor = e.target.result; - if (cursor) { - results.push(cursor.value); - cursor.continue(); - } else { - resolve(results); - } - }; - } - - _getAllKeysFast(objectStoreOrIndex, query, resolve, reject) { - const request = objectStoreOrIndex.getAllKeys(query); - request.onerror = (e) => reject(e.target.error); - request.onsuccess = (e) => resolve(e.target.result); - } - - _getAllKeysUsingCursor(objectStoreOrIndex, query, resolve, reject) { - const results = []; - const request = objectStoreOrIndex.openKeyCursor(query, 'next'); - request.onerror = (e) => reject(e.target.error); - request.onsuccess = (e) => { - const cursor = e.target.result; - if (cursor) { - results.push(cursor.primaryKey); - cursor.continue(); - } else { - resolve(results); - } - }; - } - - _bulkDeleteInternal(objectStore, keys, onProgress, resolve, reject) { - const count = keys.length; - if (count === 0) { - resolve(); - return; - } - - let completedCount = 0; - const hasProgress = (typeof onProgress === 'function'); - - const onError = (e) => reject(e.target.error); - const onSuccess = () => { - ++completedCount; - if (hasProgress) { - try { - onProgress(completedCount, count); - } catch (e) { - // NOP - } - } - if (completedCount >= count) { - resolve(); - } - }; - - for (const key of keys) { - const request = objectStore.delete(key); - request.onerror = onError; - request.onsuccess = onSuccess; - } - } -} -- cgit v1.2.3