From 7bad83e01cb4bb65dd544de127224ba453b8e4eb Mon Sep 17 00:00:00 2001 From: forsakeninfinity Date: Sat, 8 Jul 2023 04:04:42 -0700 Subject: Add support for exporting and importing dictionaries database. It's super annoying to have to import dictionaries one at a time every time you move across browsers or devices. This change adds an experimental mechanism to export and import the entire database of dictionaries so that users have to deal with only one source instead of tracking tens of different dictionaries separately when migrating. --- ext/js/pages/settings/backup-controller.js | 130 +++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) (limited to 'ext/js/pages') diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index b4de01bf..bd089fb1 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -18,6 +18,7 @@ /* global * ArrayBufferUtil + * Dexie * DictionaryController * OptionsUtil */ @@ -33,6 +34,10 @@ class BackupController { this._settingsImportErrorModal = null; this._settingsImportWarningModal = null; this._optionsUtil = null; + + this._dictionariesDatabaseName = 'dict'; + this._settingsExportDatabaseToken = null; + try { this._optionsUtil = new OptionsUtil(); } catch (e) { @@ -56,6 +61,10 @@ class BackupController { this._addNodeEventListener('#settings-import-file', 'change', this._onSettingsImportFileChange.bind(this), false); this._addNodeEventListener('#settings-reset-button', 'click', this._onSettingsResetClick.bind(this), false); this._addNodeEventListener('#settings-reset-confirm-button', 'click', this._onSettingsResetConfirmClick.bind(this), false); + + this._addNodeEventListener('#settings-export-db-button', 'click', this._onSettingsExportDatabaseClick.bind(this), false); + this._addNodeEventListener('#settings-import-db-button', 'click', this._onSettingsImportDatabaseClick.bind(this), false); + this._addNodeEventListener('#settings-import-db', 'change', this._onSettingsImportDatabaseChange.bind(this), false); } // Private @@ -413,4 +422,125 @@ class BackupController { log.error(e); } } + + // Exporting Dictionaries Database + + _databaseExportImportErrorMessage(message, isWarning=false) { + const errorMessageContainer = document.querySelector('#db-ops-error-report'); + errorMessageContainer.style.display = 'block'; + errorMessageContainer.textContent = message; + + if (isWarning) { // Hide after 5 seconds (5000 milliseconds) + errorMessageContainer.style.color = '#FFC40C'; + setTimeout(function _hideWarningMessage() { + errorMessageContainer.style.display = 'none'; + errorMessageContainer.style.color = '#8B0000'; + }, 5000); + } + } + + _databaseExportProgressCallback({totalRows, completedRows, done}) { + console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); + const messageContainer = document.querySelector('#db-ops-progress-report'); + messageContainer.style.display = 'block'; + messageContainer.textContent = `Export Progress: ${completedRows} of ${totalRows} rows completed`; + + if (done) { + console.log('Done exporting.'); + messageContainer.style.display = 'none'; + } + } + + async _exportDatabase(databaseName) { + const db = await new Dexie(databaseName).open(); + const blob = await db.export({progressCallback: this._databaseExportProgressCallback}); + await db.close(); + return blob; + } + + async _onSettingsExportDatabaseClick() { + if (this._settingsExportDatabaseToken !== null) { + // An existing import or export is in progress. + this._databaseExportImportErrorMessage('An export or import operation is already in progress. Please wait till it is over.', true); + return; + } + + const errorMessageContainer = document.querySelector('#db-ops-error-report'); + errorMessageContainer.style.display = 'none'; + + const date = new Date(Date.now()); + const pageExitPrevention = this._settingsController.preventPageExit(); + try { + const token = {}; + this._settingsExportDatabaseToken = token; + const fileName = `yomitan-dictionaries-${this._getSettingsExportDateString(date, '-', '-', '-', 6)}.json`; + const data = await this._exportDatabase(this._dictionariesDatabaseName); + const blob = new Blob([data], {type: 'application/json'}); + this._saveBlob(blob, fileName); + } catch (error) { + console.log(error); + this._databaseExportImportErrorMessage('Errors encountered while exporting. Please try again. Restart the browser if it continues to fail.'); + } finally { + pageExitPrevention.end(); + this._settingsExportDatabaseToken = null; + } + } + + // Importing Dictionaries Database + + _databaseImportProgressCallback({totalRows, completedRows, done}) { + console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); + const messageContainer = document.querySelector('#db-ops-progress-report'); + messageContainer.style.display = 'block'; + messageContainer.style.color = '#4169e1'; + messageContainer.textContent = `Import Progress: ${completedRows} of ${totalRows} rows completed`; + + if (done) { + console.log('Done importing.'); + messageContainer.style.color = '#006633'; + messageContainer.textContent = 'Done importing. You will need to re-enable the dictionaries and refresh afterward. If you run into issues, please restart the browser. If it continues to fail, reinstall Yomitan and import dictionaries one-by-one.'; + } + } + + async _importDatabase(databaseName, file) { + await yomichan.api.purgeDatabase(); + await Dexie.import(file, {progressCallback: this._databaseImportProgressCallback}); + yomichan.api.triggerDatabaseUpdated('dictionary', 'import'); + yomichan.trigger('storageChanged'); + } + + _onSettingsImportDatabaseClick() { + document.querySelector('#settings-import-db').click(); + } + + async _onSettingsImportDatabaseChange(e) { + if (this._settingsExportDatabaseToken !== null) { + // An existing import or export is in progress. + this._databaseExportImportErrorMessage('An export or import operation is already in progress. Please wait till it is over.', true); + return; + } + + const errorMessageContainer = document.querySelector('#db-ops-error-report'); + errorMessageContainer.style.display = 'none'; + + const files = e.target.files; + if (files.length === 0) { return; } + + const pageExitPrevention = this._settingsController.preventPageExit(); + const file = files[0]; + e.target.value = null; + try { + const token = {}; + this._settingsExportDatabaseToken = token; + await this._importDatabase(this._dictionariesDatabaseName, file); + } catch (error) { + console.log(error); + const messageContainer = document.querySelector('#db-ops-progress-report'); + messageContainer.style.color = 'red'; + this._databaseExportImportErrorMessage('Encountered errors when importing. Please restart the browser and try again. If it continues to fail, reinstall Yomitan and import dictionaries one-by-one.'); + } finally { + pageExitPrevention.end(); + this._settingsExportDatabaseToken = null; + } + } } -- cgit v1.2.3