From 441c23bf3be1bc4f14e17ec3956a8c90b1a674e8 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 28 Jun 2020 17:24:06 -0400 Subject: Rename Database to DictionaryDatabase (#633) --- ext/bg/background.html | 2 +- ext/bg/js/backend.js | 18 +- ext/bg/js/database.js | 474 --------------------------------------- ext/bg/js/dictionary-database.js | 474 +++++++++++++++++++++++++++++++++++++++ ext/bg/js/dictionary-importer.js | 12 +- 5 files changed, 490 insertions(+), 490 deletions(-) delete mode 100644 ext/bg/js/database.js create mode 100644 ext/bg/js/dictionary-database.js (limited to 'ext') diff --git a/ext/bg/background.html b/ext/bg/background.html index 11ea002f..0591032d 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -32,7 +32,7 @@ - + diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 547b32d0..4791bfb5 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -21,7 +21,7 @@ * AudioSystem * AudioUriBuilder * ClipboardMonitor - * Database + * DictionaryDatabase * DictionaryImporter * Environment * JsonSchema @@ -43,9 +43,9 @@ class Backend { constructor() { this._environment = new Environment(); - this._database = new Database(); + this._dictionaryDatabase = new DictionaryDatabase(); this._dictionaryImporter = new DictionaryImporter(); - this._translator = new Translator(this._database); + this._translator = new Translator(this._dictionaryDatabase); this._anki = new AnkiConnect(); this._mecab = new Mecab(); this._clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)}); @@ -193,7 +193,7 @@ class Backend { await this._environment.prepare(); try { - await this._database.prepare(); + await this._dictionaryDatabase.prepare(); } catch (e) { yomichan.logError(e); } @@ -709,11 +709,11 @@ class Backend { async _onApiPurgeDatabase() { this._translator.clearDatabaseCaches(); - await this._database.purge(); + await this._dictionaryDatabase.purge(); } async _onApiGetMedia({targets}) { - return await this._database.getMedia(targets); + return await this._dictionaryDatabase.getMedia(targets); } _onApiLog({error, level, context}) { @@ -747,12 +747,12 @@ class Backend { } async _onApiImportDictionaryArchive({archiveContent, details}, sender, onProgress) { - return await this._dictionaryImporter.import(this._database, archiveContent, details, onProgress); + return await this._dictionaryImporter.import(this._dictionaryDatabase, archiveContent, details, onProgress); } async _onApiDeleteDictionary({dictionaryName}, sender, onProgress) { this._translator.clearDatabaseCaches(); - await this._database.deleteDictionary(dictionaryName, {rate: 1000}, onProgress); + await this._dictionaryDatabase.deleteDictionary(dictionaryName, {rate: 1000}, onProgress); } async _onApiModifySettings({targets, source}) { @@ -966,7 +966,7 @@ class Backend { } async _importDictionary(archiveSource, onProgress, details) { - return await this._dictionaryImporter.import(this._database, archiveSource, onProgress, details); + return await this._dictionaryImporter.import(this._dictionaryDatabase, archiveSource, onProgress, details); } async _textParseScanning(text, options) { diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js deleted file mode 100644 index 47f1ebdd..00000000 --- a/ext/bg/js/database.js +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Copyright (C) 2016-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 . - */ - -/* global - * GenericDatabase - * dictFieldSplit - */ - -class Database { - constructor() { - this._db = new GenericDatabase(); - this._dbName = 'dict'; - this._schemas = new Map(); - } - - // Public - - async prepare() { - await this._db.open( - this._dbName, - 60, - [ - { - version: 20, - stores: { - terms: { - primaryKey: {keyPath: 'id', autoIncrement: true}, - indices: ['dictionary', 'expression', 'reading'] - }, - kanji: { - primaryKey: {autoIncrement: true}, - indices: ['dictionary', 'character'] - }, - tagMeta: { - primaryKey: {autoIncrement: true}, - indices: ['dictionary'] - }, - dictionaries: { - primaryKey: {autoIncrement: true}, - indices: ['title', 'version'] - } - } - }, - { - version: 30, - stores: { - termMeta: { - primaryKey: {autoIncrement: true}, - indices: ['dictionary', 'expression'] - }, - kanjiMeta: { - primaryKey: {autoIncrement: true}, - indices: ['dictionary', 'character'] - }, - tagMeta: { - primaryKey: {autoIncrement: true}, - indices: ['dictionary', 'name'] - } - } - }, - { - version: 40, - stores: { - terms: { - primaryKey: {keyPath: 'id', autoIncrement: true}, - indices: ['dictionary', 'expression', 'reading', 'sequence'] - } - } - }, - { - version: 50, - stores: { - terms: { - primaryKey: {keyPath: 'id', autoIncrement: true}, - indices: ['dictionary', 'expression', 'reading', 'sequence', 'expressionReverse', 'readingReverse'] - } - } - }, - { - version: 60, - stores: { - media: { - primaryKey: {keyPath: 'id', autoIncrement: true}, - indices: ['dictionary', 'path'] - } - } - } - ] - ); - } - - async close() { - this._db.close(); - } - - isPrepared() { - return this._db.isOpen(); - } - - async purge() { - if (this._db.isOpening()) { - throw new Error('Cannot purge database while opening'); - } - if (this._db.isOpen()) { - this._db.close(); - } - await GenericDatabase.deleteDatabase(this._dbName); - await this.prepare(); - } - - async deleteDictionary(dictionaryName, progressSettings, onProgress) { - const targets = [ - ['dictionaries', 'title'], - ['kanji', 'dictionary'], - ['kanjiMeta', 'dictionary'], - ['terms', 'dictionary'], - ['termMeta', 'dictionary'], - ['tagMeta', 'dictionary'], - ['media', 'dictionary'] - ]; - - const {rate} = progressSettings; - const progressData = { - count: 0, - processed: 0, - storeCount: targets.length, - storesProcesed: 0 - }; - - const filterKeys = (keys) => { - ++progressData.storesProcesed; - progressData.count += keys.length; - onProgress(progressData); - return keys; - }; - const onProgress2 = () => { - const processed = progressData.processed + 1; - progressData.processed = processed; - if ((processed % rate) === 0 || processed === progressData.count) { - onProgress(progressData); - } - }; - - const promises = []; - for (const [objectStoreName, indexName] of targets) { - const query = IDBKeyRange.only(dictionaryName); - const promise = this._db.bulkDelete(objectStoreName, indexName, query, filterKeys, onProgress2); - promises.push(promise); - } - await Promise.all(promises); - } - - findTermsBulk(termList, dictionaries, wildcard) { - return new Promise((resolve, reject) => { - const results = []; - const count = termList.length; - if (count === 0) { - resolve(results); - return; - } - - const visited = new Set(); - const useWildcard = !!wildcard; - const prefixWildcard = wildcard === 'prefix'; - - const transaction = this._db.transaction(['terms'], 'readonly'); - const terms = transaction.objectStore('terms'); - const index1 = terms.index(prefixWildcard ? 'expressionReverse' : 'expression'); - const index2 = terms.index(prefixWildcard ? 'readingReverse' : 'reading'); - - const count2 = count * 2; - let completeCount = 0; - for (let i = 0; i < count; ++i) { - const inputIndex = i; - const term = prefixWildcard ? stringReverse(termList[i]) : termList[i]; - const query = useWildcard ? IDBKeyRange.bound(term, `${term}\uffff`, false, false) : IDBKeyRange.only(term); - - const onGetAll = (rows) => { - for (const row of rows) { - if (dictionaries.has(row.dictionary) && !visited.has(row.id)) { - visited.add(row.id); - results.push(this._createTerm(row, inputIndex)); - } - } - if (++completeCount >= count2) { - resolve(results); - } - }; - - this._db.getAll(index1, query, onGetAll, reject); - this._db.getAll(index2, query, onGetAll, reject); - } - }); - } - - findTermsExactBulk(termList, readingList, dictionaries) { - return new Promise((resolve, reject) => { - const results = []; - const count = termList.length; - if (count === 0) { - resolve(results); - return; - } - - const transaction = this._db.transaction(['terms'], 'readonly'); - const terms = transaction.objectStore('terms'); - const index = terms.index('expression'); - - let completeCount = 0; - for (let i = 0; i < count; ++i) { - const inputIndex = i; - const reading = readingList[i]; - const query = IDBKeyRange.only(termList[i]); - - const onGetAll = (rows) => { - for (const row of rows) { - if (row.reading === reading && dictionaries.has(row.dictionary)) { - results.push(this._createTerm(row, inputIndex)); - } - } - if (++completeCount >= count) { - resolve(results); - } - }; - - this._db.getAll(index, query, onGetAll, reject); - } - }); - } - - findTermsBySequenceBulk(sequenceList, mainDictionary) { - return new Promise((resolve, reject) => { - const results = []; - const count = sequenceList.length; - if (count === 0) { - resolve(results); - return; - } - - const transaction = this._db.transaction(['terms'], 'readonly'); - const terms = transaction.objectStore('terms'); - const index = terms.index('sequence'); - - let completeCount = 0; - for (let i = 0; i < count; ++i) { - const inputIndex = i; - const query = IDBKeyRange.only(sequenceList[i]); - - const onGetAll = (rows) => { - for (const row of rows) { - if (row.dictionary === mainDictionary) { - results.push(this._createTerm(row, inputIndex)); - } - } - if (++completeCount >= count) { - resolve(results); - } - }; - - this._db.getAll(index, query, onGetAll, reject); - } - }); - } - - findTermMetaBulk(termList, dictionaries) { - return this._findGenericBulk('termMeta', 'expression', termList, dictionaries, this._createTermMeta.bind(this)); - } - - findKanjiBulk(kanjiList, dictionaries) { - return this._findGenericBulk('kanji', 'character', kanjiList, dictionaries, this._createKanji.bind(this)); - } - - findKanjiMetaBulk(kanjiList, dictionaries) { - return this._findGenericBulk('kanjiMeta', 'character', kanjiList, dictionaries, this._createKanjiMeta.bind(this)); - } - - findTagForTitle(name, title) { - const query = IDBKeyRange.only(name); - return this._db.find('tagMeta', 'name', query, (row) => (row.dictionary === title), null); - } - - getMedia(targets) { - return new Promise((resolve, reject) => { - const count = targets.length; - const results = new Array(count).fill(null); - if (count === 0) { - resolve(results); - return; - } - - let completeCount = 0; - const transaction = this._db.transaction(['media'], 'readonly'); - const objectStore = transaction.objectStore('media'); - const index = objectStore.index('path'); - - for (let i = 0; i < count; ++i) { - const inputIndex = i; - const {path, dictionaryName} = targets[i]; - const query = IDBKeyRange.only(path); - - const onGetAll = (rows) => { - for (const row of rows) { - if (row.dictionary !== dictionaryName) { continue; } - results[inputIndex] = this._createMedia(row, inputIndex); - } - if (++completeCount >= count) { - resolve(results); - } - }; - - this._db.getAll(index, query, onGetAll, reject); - } - }); - } - - getDictionaryInfo() { - return new Promise((resolve, reject) => { - const transaction = this._db.transaction(['dictionaries'], 'readonly'); - const objectStore = transaction.objectStore('dictionaries'); - this._db.getAll(objectStore, null, resolve, reject); - }); - } - - getDictionaryCounts(dictionaryNames, getTotal) { - return new Promise((resolve, reject) => { - const targets = [ - ['kanji', 'dictionary'], - ['kanjiMeta', 'dictionary'], - ['terms', 'dictionary'], - ['termMeta', 'dictionary'], - ['tagMeta', 'dictionary'], - ['media', 'dictionary'] - ]; - const objectStoreNames = targets.map(([objectStoreName]) => objectStoreName); - const transaction = this._db.transaction(objectStoreNames, 'readonly'); - const databaseTargets = targets.map(([objectStoreName, indexName]) => { - const objectStore = transaction.objectStore(objectStoreName); - const index = objectStore.index(indexName); - return {objectStore, index}; - }); - - const countTargets = []; - if (getTotal) { - for (const {objectStore} of databaseTargets) { - countTargets.push([objectStore, null]); - } - } - for (const dictionaryName of dictionaryNames) { - const query = IDBKeyRange.only(dictionaryName); - for (const {index} of databaseTargets) { - countTargets.push([index, query]); - } - } - - const onCountComplete = (results) => { - const resultCount = results.length; - const targetCount = targets.length; - const counts = []; - for (let i = 0; i < resultCount; i += targetCount) { - const countGroup = {}; - for (let j = 0; j < targetCount; ++j) { - countGroup[targets[j][0]] = results[i + j]; - } - counts.push(countGroup); - } - const total = getTotal ? counts.shift() : null; - resolve({total, counts}); - }; - - this._db.bulkCount(countTargets, onCountComplete, reject); - }); - } - - async dictionaryExists(title) { - const query = IDBKeyRange.only(title); - const result = await this._db.find('dictionaries', 'title', query); - return typeof result !== 'undefined'; - } - - bulkAdd(objectStoreName, items, start, count) { - return this._db.bulkAdd(objectStoreName, items, start, count); - } - - // Private - - async _findGenericBulk(objectStoreName, indexName, indexValueList, dictionaries, createResult) { - return new Promise((resolve, reject) => { - const results = []; - const count = indexValueList.length; - if (count === 0) { - resolve(results); - return; - } - - const transaction = this._db.transaction([objectStoreName], 'readonly'); - const terms = transaction.objectStore(objectStoreName); - const index = terms.index(indexName); - - let completeCount = 0; - for (let i = 0; i < count; ++i) { - const inputIndex = i; - const query = IDBKeyRange.only(indexValueList[i]); - - const onGetAll = (rows) => { - for (const row of rows) { - if (dictionaries.has(row.dictionary)) { - results.push(createResult(row, inputIndex)); - } - } - if (++completeCount >= count) { - resolve(results); - } - }; - - this._db.getAll(index, query, onGetAll, reject); - } - }); - } - - _createTerm(row, index) { - return { - index, - expression: row.expression, - reading: row.reading, - definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''), - termTags: dictFieldSplit(row.termTags || ''), - rules: dictFieldSplit(row.rules), - glossary: row.glossary, - score: row.score, - dictionary: row.dictionary, - id: row.id, - sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence - }; - } - - _createKanji(row, index) { - return { - index, - character: row.character, - onyomi: dictFieldSplit(row.onyomi), - kunyomi: dictFieldSplit(row.kunyomi), - tags: dictFieldSplit(row.tags), - glossary: row.meanings, - stats: row.stats, - dictionary: row.dictionary - }; - } - - _createTermMeta({expression, mode, data, dictionary}, index) { - return {expression, mode, data, dictionary, index}; - } - - _createKanjiMeta({character, mode, data, dictionary}, index) { - return {character, mode, data, dictionary, index}; - } - - _createMedia(row, index) { - return Object.assign({}, row, {index}); - } -} diff --git a/ext/bg/js/dictionary-database.js b/ext/bg/js/dictionary-database.js new file mode 100644 index 00000000..c48320cd --- /dev/null +++ b/ext/bg/js/dictionary-database.js @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2016-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 . + */ + +/* global + * GenericDatabase + * dictFieldSplit + */ + +class DictionaryDatabase { + constructor() { + this._db = new GenericDatabase(); + this._dbName = 'dict'; + this._schemas = new Map(); + } + + // Public + + async prepare() { + await this._db.open( + this._dbName, + 60, + [ + { + version: 20, + stores: { + terms: { + primaryKey: {keyPath: 'id', autoIncrement: true}, + indices: ['dictionary', 'expression', 'reading'] + }, + kanji: { + primaryKey: {autoIncrement: true}, + indices: ['dictionary', 'character'] + }, + tagMeta: { + primaryKey: {autoIncrement: true}, + indices: ['dictionary'] + }, + dictionaries: { + primaryKey: {autoIncrement: true}, + indices: ['title', 'version'] + } + } + }, + { + version: 30, + stores: { + termMeta: { + primaryKey: {autoIncrement: true}, + indices: ['dictionary', 'expression'] + }, + kanjiMeta: { + primaryKey: {autoIncrement: true}, + indices: ['dictionary', 'character'] + }, + tagMeta: { + primaryKey: {autoIncrement: true}, + indices: ['dictionary', 'name'] + } + } + }, + { + version: 40, + stores: { + terms: { + primaryKey: {keyPath: 'id', autoIncrement: true}, + indices: ['dictionary', 'expression', 'reading', 'sequence'] + } + } + }, + { + version: 50, + stores: { + terms: { + primaryKey: {keyPath: 'id', autoIncrement: true}, + indices: ['dictionary', 'expression', 'reading', 'sequence', 'expressionReverse', 'readingReverse'] + } + } + }, + { + version: 60, + stores: { + media: { + primaryKey: {keyPath: 'id', autoIncrement: true}, + indices: ['dictionary', 'path'] + } + } + } + ] + ); + } + + async close() { + this._db.close(); + } + + isPrepared() { + return this._db.isOpen(); + } + + async purge() { + if (this._db.isOpening()) { + throw new Error('Cannot purge database while opening'); + } + if (this._db.isOpen()) { + this._db.close(); + } + await GenericDatabase.deleteDatabase(this._dbName); + await this.prepare(); + } + + async deleteDictionary(dictionaryName, progressSettings, onProgress) { + const targets = [ + ['dictionaries', 'title'], + ['kanji', 'dictionary'], + ['kanjiMeta', 'dictionary'], + ['terms', 'dictionary'], + ['termMeta', 'dictionary'], + ['tagMeta', 'dictionary'], + ['media', 'dictionary'] + ]; + + const {rate} = progressSettings; + const progressData = { + count: 0, + processed: 0, + storeCount: targets.length, + storesProcesed: 0 + }; + + const filterKeys = (keys) => { + ++progressData.storesProcesed; + progressData.count += keys.length; + onProgress(progressData); + return keys; + }; + const onProgress2 = () => { + const processed = progressData.processed + 1; + progressData.processed = processed; + if ((processed % rate) === 0 || processed === progressData.count) { + onProgress(progressData); + } + }; + + const promises = []; + for (const [objectStoreName, indexName] of targets) { + const query = IDBKeyRange.only(dictionaryName); + const promise = this._db.bulkDelete(objectStoreName, indexName, query, filterKeys, onProgress2); + promises.push(promise); + } + await Promise.all(promises); + } + + findTermsBulk(termList, dictionaries, wildcard) { + return new Promise((resolve, reject) => { + const results = []; + const count = termList.length; + if (count === 0) { + resolve(results); + return; + } + + const visited = new Set(); + const useWildcard = !!wildcard; + const prefixWildcard = wildcard === 'prefix'; + + const transaction = this._db.transaction(['terms'], 'readonly'); + const terms = transaction.objectStore('terms'); + const index1 = terms.index(prefixWildcard ? 'expressionReverse' : 'expression'); + const index2 = terms.index(prefixWildcard ? 'readingReverse' : 'reading'); + + const count2 = count * 2; + let completeCount = 0; + for (let i = 0; i < count; ++i) { + const inputIndex = i; + const term = prefixWildcard ? stringReverse(termList[i]) : termList[i]; + const query = useWildcard ? IDBKeyRange.bound(term, `${term}\uffff`, false, false) : IDBKeyRange.only(term); + + const onGetAll = (rows) => { + for (const row of rows) { + if (dictionaries.has(row.dictionary) && !visited.has(row.id)) { + visited.add(row.id); + results.push(this._createTerm(row, inputIndex)); + } + } + if (++completeCount >= count2) { + resolve(results); + } + }; + + this._db.getAll(index1, query, onGetAll, reject); + this._db.getAll(index2, query, onGetAll, reject); + } + }); + } + + findTermsExactBulk(termList, readingList, dictionaries) { + return new Promise((resolve, reject) => { + const results = []; + const count = termList.length; + if (count === 0) { + resolve(results); + return; + } + + const transaction = this._db.transaction(['terms'], 'readonly'); + const terms = transaction.objectStore('terms'); + const index = terms.index('expression'); + + let completeCount = 0; + for (let i = 0; i < count; ++i) { + const inputIndex = i; + const reading = readingList[i]; + const query = IDBKeyRange.only(termList[i]); + + const onGetAll = (rows) => { + for (const row of rows) { + if (row.reading === reading && dictionaries.has(row.dictionary)) { + results.push(this._createTerm(row, inputIndex)); + } + } + if (++completeCount >= count) { + resolve(results); + } + }; + + this._db.getAll(index, query, onGetAll, reject); + } + }); + } + + findTermsBySequenceBulk(sequenceList, mainDictionary) { + return new Promise((resolve, reject) => { + const results = []; + const count = sequenceList.length; + if (count === 0) { + resolve(results); + return; + } + + const transaction = this._db.transaction(['terms'], 'readonly'); + const terms = transaction.objectStore('terms'); + const index = terms.index('sequence'); + + let completeCount = 0; + for (let i = 0; i < count; ++i) { + const inputIndex = i; + const query = IDBKeyRange.only(sequenceList[i]); + + const onGetAll = (rows) => { + for (const row of rows) { + if (row.dictionary === mainDictionary) { + results.push(this._createTerm(row, inputIndex)); + } + } + if (++completeCount >= count) { + resolve(results); + } + }; + + this._db.getAll(index, query, onGetAll, reject); + } + }); + } + + findTermMetaBulk(termList, dictionaries) { + return this._findGenericBulk('termMeta', 'expression', termList, dictionaries, this._createTermMeta.bind(this)); + } + + findKanjiBulk(kanjiList, dictionaries) { + return this._findGenericBulk('kanji', 'character', kanjiList, dictionaries, this._createKanji.bind(this)); + } + + findKanjiMetaBulk(kanjiList, dictionaries) { + return this._findGenericBulk('kanjiMeta', 'character', kanjiList, dictionaries, this._createKanjiMeta.bind(this)); + } + + findTagForTitle(name, title) { + const query = IDBKeyRange.only(name); + return this._db.find('tagMeta', 'name', query, (row) => (row.dictionary === title), null); + } + + getMedia(targets) { + return new Promise((resolve, reject) => { + const count = targets.length; + const results = new Array(count).fill(null); + if (count === 0) { + resolve(results); + return; + } + + let completeCount = 0; + const transaction = this._db.transaction(['media'], 'readonly'); + const objectStore = transaction.objectStore('media'); + const index = objectStore.index('path'); + + for (let i = 0; i < count; ++i) { + const inputIndex = i; + const {path, dictionaryName} = targets[i]; + const query = IDBKeyRange.only(path); + + const onGetAll = (rows) => { + for (const row of rows) { + if (row.dictionary !== dictionaryName) { continue; } + results[inputIndex] = this._createMedia(row, inputIndex); + } + if (++completeCount >= count) { + resolve(results); + } + }; + + this._db.getAll(index, query, onGetAll, reject); + } + }); + } + + getDictionaryInfo() { + return new Promise((resolve, reject) => { + const transaction = this._db.transaction(['dictionaries'], 'readonly'); + const objectStore = transaction.objectStore('dictionaries'); + this._db.getAll(objectStore, null, resolve, reject); + }); + } + + getDictionaryCounts(dictionaryNames, getTotal) { + return new Promise((resolve, reject) => { + const targets = [ + ['kanji', 'dictionary'], + ['kanjiMeta', 'dictionary'], + ['terms', 'dictionary'], + ['termMeta', 'dictionary'], + ['tagMeta', 'dictionary'], + ['media', 'dictionary'] + ]; + const objectStoreNames = targets.map(([objectStoreName]) => objectStoreName); + const transaction = this._db.transaction(objectStoreNames, 'readonly'); + const databaseTargets = targets.map(([objectStoreName, indexName]) => { + const objectStore = transaction.objectStore(objectStoreName); + const index = objectStore.index(indexName); + return {objectStore, index}; + }); + + const countTargets = []; + if (getTotal) { + for (const {objectStore} of databaseTargets) { + countTargets.push([objectStore, null]); + } + } + for (const dictionaryName of dictionaryNames) { + const query = IDBKeyRange.only(dictionaryName); + for (const {index} of databaseTargets) { + countTargets.push([index, query]); + } + } + + const onCountComplete = (results) => { + const resultCount = results.length; + const targetCount = targets.length; + const counts = []; + for (let i = 0; i < resultCount; i += targetCount) { + const countGroup = {}; + for (let j = 0; j < targetCount; ++j) { + countGroup[targets[j][0]] = results[i + j]; + } + counts.push(countGroup); + } + const total = getTotal ? counts.shift() : null; + resolve({total, counts}); + }; + + this._db.bulkCount(countTargets, onCountComplete, reject); + }); + } + + async dictionaryExists(title) { + const query = IDBKeyRange.only(title); + const result = await this._db.find('dictionaries', 'title', query); + return typeof result !== 'undefined'; + } + + bulkAdd(objectStoreName, items, start, count) { + return this._db.bulkAdd(objectStoreName, items, start, count); + } + + // Private + + async _findGenericBulk(objectStoreName, indexName, indexValueList, dictionaries, createResult) { + return new Promise((resolve, reject) => { + const results = []; + const count = indexValueList.length; + if (count === 0) { + resolve(results); + return; + } + + const transaction = this._db.transaction([objectStoreName], 'readonly'); + const terms = transaction.objectStore(objectStoreName); + const index = terms.index(indexName); + + let completeCount = 0; + for (let i = 0; i < count; ++i) { + const inputIndex = i; + const query = IDBKeyRange.only(indexValueList[i]); + + const onGetAll = (rows) => { + for (const row of rows) { + if (dictionaries.has(row.dictionary)) { + results.push(createResult(row, inputIndex)); + } + } + if (++completeCount >= count) { + resolve(results); + } + }; + + this._db.getAll(index, query, onGetAll, reject); + } + }); + } + + _createTerm(row, index) { + return { + index, + expression: row.expression, + reading: row.reading, + definitionTags: dictFieldSplit(row.definitionTags || row.tags || ''), + termTags: dictFieldSplit(row.termTags || ''), + rules: dictFieldSplit(row.rules), + glossary: row.glossary, + score: row.score, + dictionary: row.dictionary, + id: row.id, + sequence: typeof row.sequence === 'undefined' ? -1 : row.sequence + }; + } + + _createKanji(row, index) { + return { + index, + character: row.character, + onyomi: dictFieldSplit(row.onyomi), + kunyomi: dictFieldSplit(row.kunyomi), + tags: dictFieldSplit(row.tags), + glossary: row.meanings, + stats: row.stats, + dictionary: row.dictionary + }; + } + + _createTermMeta({expression, mode, data, dictionary}, index) { + return {expression, mode, data, dictionary, index}; + } + + _createKanjiMeta({character, mode, data, dictionary}, index) { + return {character, mode, data, dictionary, index}; + } + + _createMedia(row, index) { + return Object.assign({}, row, {index}); + } +} diff --git a/ext/bg/js/dictionary-importer.js b/ext/bg/js/dictionary-importer.js index 10e30cec..12f3129d 100644 --- a/ext/bg/js/dictionary-importer.js +++ b/ext/bg/js/dictionary-importer.js @@ -27,11 +27,11 @@ class DictionaryImporter { this._schemas = new Map(); } - async import(database, archiveSource, details, onProgress) { - if (!database) { + async import(dictionaryDatabase, archiveSource, details, onProgress) { + if (!dictionaryDatabase) { throw new Error('Invalid database'); } - if (!database.isPrepared()) { + if (!dictionaryDatabase.isPrepared()) { throw new Error('Database is not ready'); } @@ -60,7 +60,7 @@ class DictionaryImporter { } // Verify database is not already imported - if (await database.dictionaryExists(dictionaryTitle)) { + if (await dictionaryDatabase.dictionaryExists(dictionaryTitle)) { throw new Error('Dictionary is already imported'); } @@ -168,7 +168,7 @@ class DictionaryImporter { // Add dictionary const summary = this._createSummary(dictionaryTitle, version, index, {prefixWildcardsSupported}); - database.bulkAdd('dictionaries', [summary], 0, 1); + dictionaryDatabase.bulkAdd('dictionaries', [summary], 0, 1); // Add data const errors = []; @@ -188,7 +188,7 @@ class DictionaryImporter { const count = Math.min(maxTransactionLength, ii - i); try { - await database.bulkAdd(objectStoreName, entries, i, count); + await dictionaryDatabase.bulkAdd(objectStoreName, entries, i, count); } catch (e) { errors.push(errorToJson(e)); } -- cgit v1.2.3