aboutsummaryrefslogtreecommitdiff
path: root/ext/bg/js/database.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/bg/js/database.js')
-rw-r--r--ext/bg/js/database.js296
1 files changed, 169 insertions, 127 deletions
diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js
index 4829356c..e00cb7a3 100644
--- a/ext/bg/js/database.js
+++ b/ext/bg/js/database.js
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>
*
* This program is free software: you can redistribute it and/or modify
@@ -20,59 +20,62 @@
class Database {
constructor() {
this.db = null;
- this.dbVersion = 2;
- this.tagMetaCache = {};
+ this.version = 2;
+ this.tagCache = {};
}
- sanitize() {
- const db = new Dexie('dict');
- return db.open().then(() => {
+ async sanitize() {
+ try {
+ const db = new Dexie('dict');
+ await db.open();
db.close();
- if (db.verno !== this.dbVersion) {
- return db.delete();
+ if (db.verno !== this.version) {
+ await db.delete();
}
- }).catch(() => {});
+ } catch(e) {
+ // NOP
+ }
}
- prepare() {
- if (this.db !== null) {
- return Promise.reject('database already initialized');
+ async prepare() {
+ if (this.db) {
+ throw 'database already initialized';
}
- return this.sanitize().then(() => {
- this.db = new Dexie('dict');
- this.db.version(this.dbVersion).stores({
- terms: '++id,dictionary,expression,reading',
- kanji: '++,dictionary,character',
- tagMeta: '++,dictionary',
- dictionaries: '++,title,version'
- });
+ await this.sanitize();
- return this.db.open();
+ this.db = new Dexie('dict');
+ this.db.version(this.version).stores({
+ terms: '++id,dictionary,expression,reading',
+ kanji: '++,dictionary,character',
+ tagMeta: '++,dictionary',
+ dictionaries: '++,title,version'
});
+
+ await this.db.open();
}
- purge() {
- if (this.db === null) {
- return Promise.reject('database not initialized');
+ async purge() {
+ if (!this.db) {
+ throw 'database not initialized';
}
this.db.close();
- return this.db.delete().then(() => {
- this.db = null;
- this.tagMetaCache = {};
- return this.prepare();
- });
+ await this.db.delete();
+ this.db = null;
+ this.tagCache = {};
+
+ await this.prepare();
}
- findTerms(term, dictionaries) {
- if (this.db === null) {
- return Promise.reject('database not initialized');
+ async findTerms(term, titles) {
+ if (!this.db) {
+ throw 'database not initialized';
}
const results = [];
- return this.db.terms.where('expression').equals(term).or('reading').equals(term).each(row => {
- if (dictionaries.includes(row.dictionary)) {
+ await this.db.terms.where('expression').equals(term).or('reading').equals(term).each(row => {
+ if (titles.includes(row.dictionary)) {
results.push({
expression: row.expression,
reading: row.reading,
@@ -84,25 +87,24 @@ class Database {
id: row.id
});
}
- }).then(() => {
- return this.cacheTagMeta(dictionaries);
- }).then(() => {
- for (const result of results) {
- result.tagMeta = this.tagMetaCache[result.dictionary] || {};
- }
-
- return results;
});
+
+ await this.cacheTagMeta(titles);
+ for (const result of results) {
+ result.tagMeta = this.tagCache[result.dictionary] || {};
+ }
+
+ return results;
}
- findKanji(kanji, dictionaries) {
- if (this.db === null) {
+ async findKanji(kanji, titles) {
+ if (!this.db) {
return Promise.reject('database not initialized');
}
const results = [];
- return this.db.kanji.where('character').equals(kanji).each(row => {
- if (dictionaries.includes(row.dictionary)) {
+ await this.db.kanji.where('character').equals(kanji).each(row => {
+ if (titles.includes(row.dictionary)) {
results.push({
character: row.character,
onyomi: dictFieldSplit(row.onyomi),
@@ -112,103 +114,100 @@ class Database {
dictionary: row.dictionary
});
}
- }).then(() => {
- return this.cacheTagMeta(dictionaries);
- }).then(() => {
- for (const result of results) {
- result.tagMeta = this.tagMetaCache[result.dictionary] || {};
- }
-
- return results;
});
- }
- cacheTagMeta(dictionaries) {
- if (this.db === null) {
- return Promise.reject('database not initialized');
+ await this.cacheTagMeta(titles);
+ for (const result of results) {
+ result.tagMeta = this.tagCache[result.dictionary] || {};
}
- const promises = [];
- for (const dictionary of dictionaries) {
- if (this.tagMetaCache[dictionary]) {
- continue;
- }
+ return results;
+ }
- const tagMeta = {};
- promises.push(
- this.db.tagMeta.where('dictionary').equals(dictionary).each(row => {
- tagMeta[row.name] = {category: row.category, notes: row.notes, order: row.order};
- }).then(() => {
- this.tagMetaCache[dictionary] = tagMeta;
- })
- );
+ async cacheTagMeta(titles) {
+ if (!this.db) {
+ throw 'database not initialized';
}
- return Promise.all(promises);
- }
+ for (const title of titles) {
+ if (!this.tagCache[title]) {
+ const tagMeta = {};
+ await this.db.tagMeta.where('dictionary').equals(title).each(row => {
+ tagMeta[row.name] = {category: row.category, notes: row.notes, order: row.order};
+ });
- getDictionaries() {
- if (this.db === null) {
- return Promise.reject('database not initialized');
+ this.tagCache[title] = tagMeta;
+ }
}
+ }
- return this.db.dictionaries.toArray();
+ async getDictionaries() {
+ if (this.db) {
+ return this.db.dictionaries.toArray();
+ } else {
+ throw 'database not initialized';
+ }
}
- importDictionary(archive, callback) {
- if (this.db === null) {
+ async importDictionary(archive, callback) {
+ if (!this.db) {
return Promise.reject('database not initialized');
}
let summary = null;
- const indexLoaded = (title, version, revision, tagMeta, hasTerms, hasKanji) => {
+ const indexLoaded = async (title, version, revision, tagMeta, hasTerms, hasKanji) => {
summary = {title, version, revision, hasTerms, hasKanji};
- return this.db.dictionaries.where('title').equals(title).count().then(count => {
- if (count > 0) {
- return Promise.reject(`dictionary "${title}" is already imported`);
- }
-
- return this.db.dictionaries.add({title, version, revision, hasTerms, hasKanji}).then(() => {
- const rows = [];
- for (const tag in tagMeta || {}) {
- const meta = tagMeta[tag];
- const row = dictTagSanitize({
- name: tag,
- category: meta.category,
- notes: meta.notes,
- order: meta.order,
- dictionary: title
- });
-
- rows.push(row);
- }
-
- return this.db.tagMeta.bulkAdd(rows);
+
+ const count = await this.db.dictionaries.where('title').equals(title).count();
+ if (count > 0) {
+ throw `dictionary "${title}" is already imported`;
+ }
+
+ await this.db.dictionaries.add({title, version, revision, hasTerms, hasKanji});
+
+ const rows = [];
+ for (const tag in tagMeta || {}) {
+ const meta = tagMeta[tag];
+ const row = dictTagSanitize({
+ name: tag,
+ category: meta.category,
+ notes: meta.notes,
+ order: meta.order,
+ dictionary: title
});
- });
+
+ rows.push(row);
+ }
+
+ await this.db.tagMeta.bulkAdd(rows);
};
- const termsLoaded = (title, entries, total, current) => {
- return this.db.transaction('rw', this.db.terms, function() {
- for (const [expression, reading, tags, rules, score, ...glossary] of entries) {
- this.db.terms.add({
- expression,
- reading,
- tags,
- rules,
- score,
- glossary,
- dictionary: title
- });
- }
- }).then(() => {
- if (callback) {
- callback(total, current);
- }
- });
+ const termsLoaded = async (title, entries, total, current) => {
+ if (callback) {
+ callback(total, current);
+ }
+
+ const rows = [];
+ for (const [expression, reading, tags, rules, score, ...glossary] of entries) {
+ rows.push({
+ expression,
+ reading,
+ tags,
+ rules,
+ score,
+ glossary,
+ dictionary: title
+ });
+ }
+
+ await this.db.terms.bulkAdd(rows);
};
- const kanjiLoaded = (title, entries, total, current) => {
+ const kanjiLoaded = async (title, entries, total, current) => {
+ if (callback) {
+ callback(total, current);
+ }
+
const rows = [];
for (const [character, onyomi, kunyomi, tags, ...meanings] of entries) {
rows.push({
@@ -221,13 +220,56 @@ class Database {
});
}
- return this.db.kanji.bulkAdd(rows).then(() => {
- if (callback) {
- callback(total, current);
- }
- });
+ await this.db.kanji.bulkAdd(rows);
};
- return zipLoadDb(archive, indexLoaded, termsLoaded, kanjiLoaded).then(() => summary);
+ await Database.importDictionaryZip(archive, indexLoaded, termsLoaded, kanjiLoaded);
+ return summary;
+ }
+
+ static async importDictionaryZip(archive, indexLoaded, termsLoaded, kanjiLoaded) {
+ const files = (await JSZip.loadAsync(archive)).files;
+
+ const indexFile = files['index.json'];
+ if (!indexFile) {
+ throw 'no dictionary index found in archive';
+ }
+
+ const index = JSON.parse(await indexFile.async('string'));
+ if (!index.title || !index.version || !index.revision) {
+ throw 'unrecognized dictionary format';
+ }
+
+ await indexLoaded(
+ index.title,
+ index.version,
+ index.revision,
+ index.tagMeta || {},
+ index.termBanks > 0,
+ index.kanjiBanks > 0
+ );
+
+ const banksTotal = index.termBanks + index.kanjiBanks;
+ let banksLoaded = 0;
+
+ for (let i = 1; i <= index.termBanks; ++i) {
+ const bankFile = files[`term_bank_${i}.json`];
+ if (bankFile) {
+ const bank = JSON.parse(await bankFile.async('string'));
+ await termsLoaded(index.title, bank, banksTotal, banksLoaded++);
+ } else {
+ throw 'missing term bank file';
+ }
+ }
+
+ for (let i = 1; i <= index.kanjiBanks; ++i) {
+ const bankFile = files[`kanji_bank_${i}.json`];
+ if (bankFile) {
+ const bank = JSON.parse(await bankFile.async('string'));
+ await kanjiLoaded(index.title, bank, banksTotal, banksLoaded++);
+ } else {
+ throw 'missing kanji bank file';
+ }
+ }
}
}