diff options
| -rw-r--r-- | ext/bg/js/database.js | 172 | 
1 files changed, 172 insertions, 0 deletions
| diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 7d3019bf..51e8d359 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -316,6 +316,169 @@ class Database {          return result;      } +    async importDictionaryNew(archiveSource, onProgress, details) { +        this._validate(); +        const db = this.db; +        const hasOnProgress = (typeof onProgress === 'function'); + +        // Read archive +        const archive = await JSZip.loadAsync(archiveSource); + +        // Read and validate index +        const indexFile = archive.files['index.json']; +        if (!indexFile) { +            throw new Error('No dictionary index found in archive'); +        } + +        const index = JSON.parse(await indexFile.async('string')); + +        const dictionaryTitle = index.title; +        const version = index.format || index.version; + +        if (!dictionaryTitle || !index.revision) { +            throw new Error('Unrecognized dictionary format'); +        } + +        // Verify database is not already imported +        if (await this._dictionaryExists(dictionaryTitle)) { +            throw new Error('Dictionary is already imported'); +        } + +        // Data format converters +        const convertTermBankEntry = (entry) => { +            if (version === 1) { +                const [expression, reading, definitionTags, rules, score, ...glossary] = entry; +                return {expression, reading, definitionTags, rules, score, glossary}; +            } else { +                const [expression, reading, definitionTags, rules, score, glossary, sequence, termTags] = entry; +                return {expression, reading, definitionTags, rules, score, glossary, sequence, termTags}; +            } +        }; + +        const convertTermMetaBankEntry = (entry) => { +            const [expression, mode, data] = entry; +            return {expression, mode, data}; +        }; + +        const convertKanjiBankEntry = (entry) => { +            if (version === 1) { +                const [character, onyomi, kunyomi, tags, ...meanings] = entry; +                return {character, onyomi, kunyomi, tags, meanings}; +            } else { +                const [character, onyomi, kunyomi, tags, meanings, stats] = entry; +                return {character, onyomi, kunyomi, tags, meanings, stats}; +            } +        }; + +        const convertKanjiMetaBankEntry = (entry) => { +            const [character, mode, data] = entry; +            return {character, mode, data}; +        }; + +        const convertTagBankEntry = (entry) => { +            const [name, category, order, notes, score] = entry; +            return {name, category, order, notes, score}; +        }; + +        // Archive file reading +        const readFileSequence = async (fileNameFormat, convertEntry) => { +            const results = []; +            for (let i = 1; true; ++i) { +                const fileName = fileNameFormat.replace(/\?/, `${i}`); +                const file = archive.files[fileName]; +                if (!file) { break; } + +                const entries = JSON.parse(await file.async('string')); +                for (let entry of entries) { +                    entry = convertEntry(entry); +                    entry.dictionary = dictionaryTitle; +                    results.push(entry); +                } +            } +            return results; +        }; + +        // Load data +        const termList      = await readFileSequence('term_bank_?.json',       convertTermBankEntry); +        const termMetaList  = await readFileSequence('term_meta_bank_?.json',  convertTermMetaBankEntry); +        const kanjiList     = await readFileSequence('kanji_bank_?.json',      convertKanjiBankEntry); +        const kanjiMetaList = await readFileSequence('kanji_meta_bank_?.json', convertKanjiMetaBankEntry); +        const tagList       = await readFileSequence('tag_bank_?.json',        convertTagBankEntry); + +        // Old tags +        const indexTagMeta = index.tagMeta; +        if (typeof indexTagMeta === 'object' && indexTagMeta !== null) { +            for (const name of Object.keys(indexTagMeta)) { +                const {category, order, notes, score} = indexTagMeta[name]; +                tagList.push({name, category, order, notes, score}); +            } +        } + +        // Prefix wildcard support +        const prefixWildcardsSupported = !!details.prefixWildcardsSupported; +        if (prefixWildcardsSupported) { +            for (const entry of termList) { +                entry.expressionReverse = stringReverse(entry.expression); +                entry.readingReverse = stringReverse(entry.reading); +            } +        } + +        // Add dictionary +        const summary = { +            title: dictionaryTitle, +            revision: index.revision, +            sequenced: index.sequenced, +            version, +            prefixWildcardsSupported +        }; + +        { +            const transaction = db.transaction(['dictionaries'], 'readwrite'); +            const objectStore = transaction.objectStore('dictionaries'); +            await Database._bulkAdd(objectStore, [summary], 0, 1); +        } + +        // Add data +        const errors = []; +        const total = ( +            termList.length + +            termMetaList.length + +            kanjiList.length + +            kanjiMetaList.length + +            tagList.length +        ); +        let loadedCount = 0; +        const maxTransactionLength = 1000; + +        const bulkAdd = async (objectStoreName, entries) => { +            const ii = entries.length; +            for (let i = 0; i < ii; i += maxTransactionLength) { +                const count = Math.min(maxTransactionLength, ii - i); + +                try { +                    const transaction = db.transaction([objectStoreName], 'readwrite'); +                    const objectStore = transaction.objectStore(objectStoreName); +                    await Database._bulkAdd(objectStore, entries, i, count); +                } catch (e) { +                    errors.push(e); +                } + +                loadedCount += count; +                if (hasOnProgress) { +                    onProgress(total, loadedCount); +                } +            } +        }; + +        await bulkAdd('terms', termList); +        await bulkAdd('termMeta', termMetaList); +        await bulkAdd('kanji', kanjiList); +        await bulkAdd('kanjiMeta', kanjiMetaList); +        await bulkAdd('tagMeta', tagList); + +        return {result: summary, errors}; +    } +      async importDictionary(archive, progressCallback, details) {          this._validate(); @@ -499,6 +662,15 @@ class Database {          }      } +    async _dictionaryExists(title) { +        const db = this.db; +        const dbCountTransaction = db.transaction(['dictionaries'], 'readonly'); +        const dbIndex = dbCountTransaction.objectStore('dictionaries').index('title'); +        const only = IDBKeyRange.only(title); +        const count = await Database._getCount(dbIndex, only); +        return count > 0; +    } +      async _findGenericBulk(tableName, indexName, indexValueList, titles, createResult) {          this._validate(); |