From d13e640876fc317ed49e019851faa970136393ac Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Tue, 5 Nov 2024 01:22:47 +0700 Subject: [PATCH 01/15] dictionary update queue --- .../pages/settings/dictionary-controller.js | 91 +++++++++++++++++-- .../settings/dictionary-import-controller.js | 19 ++-- types/ext/settings-controller.d.ts | 3 + 3 files changed, 100 insertions(+), 13 deletions(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index a56720937a..479b639bb5 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -122,6 +122,11 @@ class DictionaryEntry { this._enabledCheckbox.checked = value; } + /** */ + hideUpdatesAvailableButton() { + this._updatesAvailable.hidden = true; + } + /** * @returns {Promise} */ @@ -161,7 +166,8 @@ class DictionaryEntry { const bodyNode = e.detail.menu.bodyNode; const count = this._dictionaryController.dictionaryOptionCount; this._setMenuActionEnabled(bodyNode, 'moveTo', count > 1); - this._setMenuActionEnabled(bodyNode, 'delete', !this._dictionaryController.isDictionaryInDeleteQueue(this.dictionaryTitle)); + const deleteDisabled = this._dictionaryController.isDictionaryInDeleteQueue(this.dictionaryTitle) || this._dictionaryController.isUpdating; + this._setMenuActionEnabled(bodyNode, 'delete', !deleteDisabled); } /** @@ -506,8 +512,12 @@ export class DictionaryController { this._extraInfo = null; /** @type {boolean} */ this._isDeleting = false; + /** @type {boolean} */ + this._isUpdating = false; /** @type {string[]} */ this._dictionaryDeleteQueue = []; + /** @type {string[]} */ + this._dictionaryUpdateQueue = []; } /** @type {import('./modal-controller.js').ModalController} */ @@ -520,6 +530,16 @@ export class DictionaryController { return this._dictionaryEntries.length; } + /** @type {boolean} */ + get isDeleting() { + return this._isDeleting; + } + + /** @type {boolean} */ + get isUpdating() { + return this._isUpdating; + } + /** */ async prepare() { this._noDictionariesInstalledWarnings = /** @type {NodeListOf} */ (document.querySelectorAll('.no-dictionaries-installed-warning')); @@ -847,6 +867,7 @@ export class DictionaryController { delete modal.node.dataset.dictionaryTitle; void this._enqueueDictionaryDelete(title); + this._hideUpdatesAvailableButtons(); } /** @@ -863,7 +884,26 @@ export class DictionaryController { if (typeof title !== 'string') { return; } delete modal.node.dataset.dictionaryTitle; - void this._updateDictionary(title, downloadUrl); + void this._enqueueDictionaryUpdate(title, downloadUrl); + this._hideUpdatesAvailableButton(title); + } + + /** + * @param {string} title + */ + _hideUpdatesAvailableButton(title) { + for (const entry of this._dictionaryEntries) { + if (entry.dictionaryTitle === title) { + entry.hideUpdatesAvailableButton(); + } + } + } + + /** */ + _hideUpdatesAvailableButtons() { + for (const entry of this._dictionaryEntries) { + entry.hideUpdatesAvailableButton(); + } } /** @@ -954,7 +994,7 @@ export class DictionaryController { /** */ async _checkForUpdates() { - if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isDeleting) { return; } + if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isDeletingOrUpdating()) { return; } let hasUpdates; try { this._checkingUpdates = true; @@ -977,7 +1017,7 @@ export class DictionaryController { /** */ async _checkIntegrity() { - if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isDeleting) { return; } + if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isDeletingOrUpdating()) { return; } try { this._checkingIntegrity = true; @@ -1057,13 +1097,28 @@ export class DictionaryController { return this._dictionaryDeleteQueue.includes(dictionaryTitle); } + /** + * @param {string} dictionaryTitle + * @returns {boolean} + */ + isDictionaryInUpdateQueue(dictionaryTitle) { + return this._dictionaryUpdateQueue.includes(dictionaryTitle); + } + + /** + * @returns {boolean} + */ + _isDeletingOrUpdating() { + return this._isDeleting || this._isUpdating; + } + /** * @param {string} dictionaryTitle */ async _enqueueDictionaryDelete(dictionaryTitle) { if (this.isDictionaryInDeleteQueue(dictionaryTitle)) { return; } this._dictionaryDeleteQueue.push(dictionaryTitle); - if (this._isDeleting) { return; } + if (this._isDeletingOrUpdating()) { return; } while (this._dictionaryDeleteQueue.length > 0) { const title = this._dictionaryDeleteQueue[0]; if (!title) { continue; } @@ -1072,6 +1127,22 @@ export class DictionaryController { } } + /** + * @param {string} dictionaryTitle + * @param {string|undefined} downloadUrl + */ + async _enqueueDictionaryUpdate(dictionaryTitle, downloadUrl) { + if (this.isDictionaryInUpdateQueue(dictionaryTitle)) { return; } + this._dictionaryUpdateQueue.push(dictionaryTitle); + if (this._isDeletingOrUpdating()) { return; } + while (this._dictionaryUpdateQueue.length > 0) { + const title = this._dictionaryUpdateQueue[0]; + if (!title) { continue; } + await this._updateDictionary(title, downloadUrl); + void this._dictionaryUpdateQueue.shift(); + } + } + /** * @param {string} dictionaryTitle */ @@ -1132,8 +1203,9 @@ export class DictionaryController { * @param {string|undefined} downloadUrl */ async _updateDictionary(dictionaryTitle, downloadUrl) { - if (this._checkingIntegrity || this._checkingUpdates || this._isDeleting || this._dictionaries === null) { return; } + if (this._checkingIntegrity || this._checkingUpdates || this._isDeletingOrUpdating() || this._dictionaries === null) { return; } + this._isUpdating = true; const dictionaryInfo = this._dictionaries.find((entry) => entry.title === dictionaryTitle); if (typeof dictionaryInfo === 'undefined') { throw new Error('Dictionary not found'); } downloadUrl = downloadUrl ?? dictionaryInfo.downloadUrl; @@ -1156,7 +1228,12 @@ export class DictionaryController { } await this._deleteDictionary(dictionaryTitle); - this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl, profilesDictionarySettings}); + /** @type {Promise} */ + const importPromise = new Promise((resolve) => { + this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl, profilesDictionarySettings, onImportDone: resolve}); + }) + await importPromise; + this._isUpdating = false; } /** diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index ed358ff9b3..e0fa18ee2f 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -128,6 +128,7 @@ export class DictionaryImportController { await this._importDictionaries( this._generateFilesFromUrls([url], onProgress), null, + null, importProgressTracker, ); void this._recommendedDictionaryQueue.shift(); @@ -252,8 +253,8 @@ export class DictionaryImportController { /** * @param {import('settings-controller').EventArgument<'importDictionaryFromUrl'>} details */ - _onEventImportDictionaryFromUrl({url, profilesDictionarySettings}) { - void this.importFilesFromURLs(url, profilesDictionarySettings); + async _onEventImportDictionaryFromUrl({url, profilesDictionarySettings, onImportDone}) { + await this.importFilesFromURLs(url, profilesDictionarySettings, onImportDone); } /** */ @@ -313,6 +314,7 @@ export class DictionaryImportController { void this._importDictionaries( this._arrayToAsyncGenerator(fileArray), null, + null, importProgressTracker, ); } @@ -419,6 +421,7 @@ export class DictionaryImportController { void this._importDictionaries( this._arrayToAsyncGenerator(files2), null, + null, new ImportProgressTracker(this._getFileImportSteps(), files2.length), ); } @@ -427,21 +430,23 @@ export class DictionaryImportController { async _onImportFromURL() { const text = this._importURLText.value.trim(); if (!text) { return; } - await this.importFilesFromURLs(text, null); + await this.importFilesFromURLs(text, null, null); } /** * @param {string} text * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings + * @param {import('settings-controller').ImportDictionaryDoneCallback} onImportDone */ - async importFilesFromURLs(text, profilesDictionarySettings) { + async importFilesFromURLs(text, profilesDictionarySettings, onImportDone) { const urls = text.split('\n'); const importProgressTracker = new ImportProgressTracker(this._getUrlImportSteps(), urls.length); const onProgress = importProgressTracker.onProgress.bind(importProgressTracker); - void this._importDictionaries( + await this._importDictionaries( this._generateFilesFromUrls(urls, onProgress), profilesDictionarySettings, + onImportDone, importProgressTracker, ); } @@ -524,9 +529,10 @@ export class DictionaryImportController { /** * @param {AsyncGenerator} dictionaries * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings + * @param {import('settings-controller').ImportDictionaryDoneCallback} onImportDone * @param {ImportProgressTracker} importProgressTracker */ - async _importDictionaries(dictionaries, profilesDictionarySettings, importProgressTracker) { + async _importDictionaries(dictionaries, profilesDictionarySettings, onImportDone, importProgressTracker) { if (this._modifying) { return; } const statusFooter = this._statusFooter; @@ -578,6 +584,7 @@ export class DictionaryImportController { if (statusFooter !== null) { statusFooter.setTaskActive(progressSelector, false); } this._setModifying(false); this._triggerStorageChanged(); + if (onImportDone) { onImportDone(); } } } diff --git a/types/ext/settings-controller.d.ts b/types/ext/settings-controller.d.ts index d87ca5d4a0..6bbea19c10 100644 --- a/types/ext/settings-controller.d.ts +++ b/types/ext/settings-controller.d.ts @@ -32,6 +32,8 @@ type ProfileDictionarySettings = Settings.DictionaryOptions & {index: number}; export type ProfilesDictionarySettings = {[profileId: string]: ProfileDictionarySettings} | null; +export type ImportDictionaryDoneCallback = (() => void) | null; + export type Events = { optionsChanged: { options: Settings.ProfileOptions; @@ -47,6 +49,7 @@ export type Events = { importDictionaryFromUrl: { url: string; profilesDictionarySettings: ProfilesDictionarySettings; + onImportDone: ImportDictionaryDoneCallback; }; dictionaryEnabled: Record; scanInputsChanged: { From 020cc8dd17a434708973c66ec596c8ec7717ab68 Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Tue, 5 Nov 2024 01:31:02 +0700 Subject: [PATCH 02/15] naming --- ext/js/pages/settings/dictionary-controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 479b639bb5..d863d74059 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -889,11 +889,11 @@ export class DictionaryController { } /** - * @param {string} title + * @param {string} dictionaryTitle */ - _hideUpdatesAvailableButton(title) { + _hideUpdatesAvailableButton(dictionaryTitle) { for (const entry of this._dictionaryEntries) { - if (entry.dictionaryTitle === title) { + if (entry.dictionaryTitle === dictionaryTitle) { entry.hideUpdatesAvailableButton(); } } From 62d4c0d1731772e9f8f8427e64d4c0a03522c01d Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Tue, 5 Nov 2024 01:38:45 +0700 Subject: [PATCH 03/15] update conditions --- ext/js/pages/settings/dictionary-controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index d863d74059..37fe01e9ca 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -1118,7 +1118,7 @@ export class DictionaryController { async _enqueueDictionaryDelete(dictionaryTitle) { if (this.isDictionaryInDeleteQueue(dictionaryTitle)) { return; } this._dictionaryDeleteQueue.push(dictionaryTitle); - if (this._isDeletingOrUpdating()) { return; } + if (this._isDeleting) { return; } while (this._dictionaryDeleteQueue.length > 0) { const title = this._dictionaryDeleteQueue[0]; if (!title) { continue; } @@ -1134,7 +1134,7 @@ export class DictionaryController { async _enqueueDictionaryUpdate(dictionaryTitle, downloadUrl) { if (this.isDictionaryInUpdateQueue(dictionaryTitle)) { return; } this._dictionaryUpdateQueue.push(dictionaryTitle); - if (this._isDeletingOrUpdating()) { return; } + if (this._isUpdating) { return; } while (this._dictionaryUpdateQueue.length > 0) { const title = this._dictionaryUpdateQueue[0]; if (!title) { continue; } @@ -1203,7 +1203,7 @@ export class DictionaryController { * @param {string|undefined} downloadUrl */ async _updateDictionary(dictionaryTitle, downloadUrl) { - if (this._checkingIntegrity || this._checkingUpdates || this._isDeletingOrUpdating() || this._dictionaries === null) { return; } + if (this._checkingIntegrity || this._checkingUpdates || this._isUpdating || this._dictionaries === null) { return; } this._isUpdating = true; const dictionaryInfo = this._dictionaries.find((entry) => entry.title === dictionaryTitle); From bebe1e7b9bee267218f8f14e347873de2dfb31e4 Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Tue, 5 Nov 2024 01:42:29 +0700 Subject: [PATCH 04/15] lint --- ext/js/pages/settings/dictionary-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 37fe01e9ca..f9075c77a7 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -1231,7 +1231,7 @@ export class DictionaryController { /** @type {Promise} */ const importPromise = new Promise((resolve) => { this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl, profilesDictionarySettings, onImportDone: resolve}); - }) + }); await importPromise; this._isUpdating = false; } From ced4a675986b5ab5a0f6ec2f9c7321be054f7d10 Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Tue, 5 Nov 2024 10:25:07 +0700 Subject: [PATCH 05/15] remove unneeded async await --- ext/js/pages/settings/dictionary-import-controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index e0fa18ee2f..73c63bca9a 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -253,8 +253,8 @@ export class DictionaryImportController { /** * @param {import('settings-controller').EventArgument<'importDictionaryFromUrl'>} details */ - async _onEventImportDictionaryFromUrl({url, profilesDictionarySettings, onImportDone}) { - await this.importFilesFromURLs(url, profilesDictionarySettings, onImportDone); + _onEventImportDictionaryFromUrl({url, profilesDictionarySettings, onImportDone}) { + void this.importFilesFromURLs(url, profilesDictionarySettings, onImportDone); } /** */ @@ -443,7 +443,7 @@ export class DictionaryImportController { const importProgressTracker = new ImportProgressTracker(this._getUrlImportSteps(), urls.length); const onProgress = importProgressTracker.onProgress.bind(importProgressTracker); - await this._importDictionaries( + void this._importDictionaries( this._generateFilesFromUrls(urls, onProgress), profilesDictionarySettings, onImportDone, From 8a30fa9c2e8a6d9ab673ba14645d20f8794a65c7 Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Tue, 5 Nov 2024 10:50:53 +0700 Subject: [PATCH 06/15] allow queueing delete and update in 1 queue --- .../pages/settings/dictionary-controller.js | 111 ++++++------------ types/ext/dictionary-controller.d.ts | 29 +++++ 2 files changed, 62 insertions(+), 78 deletions(-) create mode 100644 types/ext/dictionary-controller.d.ts diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index f9075c77a7..8b082a9d60 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -166,7 +166,7 @@ class DictionaryEntry { const bodyNode = e.detail.menu.bodyNode; const count = this._dictionaryController.dictionaryOptionCount; this._setMenuActionEnabled(bodyNode, 'moveTo', count > 1); - const deleteDisabled = this._dictionaryController.isDictionaryInDeleteQueue(this.dictionaryTitle) || this._dictionaryController.isUpdating; + const deleteDisabled = this._dictionaryController.isDictionaryInTaskQueue(this.dictionaryTitle); this._setMenuActionEnabled(bodyNode, 'delete', !deleteDisabled); } @@ -510,14 +510,10 @@ export class DictionaryController { this._allCheckbox = querySelectorNotNull(document, '#all-dictionaries-enabled'); /** @type {?DictionaryExtraInfo} */ this._extraInfo = null; + /** @type {import('dictionary-controller.js').DictionaryTask[]} */ + this._dictionaryTaskQueue = []; /** @type {boolean} */ - this._isDeleting = false; - /** @type {boolean} */ - this._isUpdating = false; - /** @type {string[]} */ - this._dictionaryDeleteQueue = []; - /** @type {string[]} */ - this._dictionaryUpdateQueue = []; + this._isTaskQueueRunning = false; } /** @type {import('./modal-controller.js').ModalController} */ @@ -530,16 +526,6 @@ export class DictionaryController { return this._dictionaryEntries.length; } - /** @type {boolean} */ - get isDeleting() { - return this._isDeleting; - } - - /** @type {boolean} */ - get isUpdating() { - return this._isUpdating; - } - /** */ async prepare() { this._noDictionariesInstalledWarnings = /** @type {NodeListOf} */ (document.querySelectorAll('.no-dictionaries-installed-warning')); @@ -862,12 +848,12 @@ export class DictionaryController { const modal = /** @type {import('./modal.js').Modal} */ (this._deleteDictionaryModal); modal.setVisible(false); - const title = modal.node.dataset.dictionaryTitle; - if (typeof title !== 'string') { return; } + const dictionaryTitle = modal.node.dataset.dictionaryTitle; + if (typeof dictionaryTitle !== 'string') { return; } delete modal.node.dataset.dictionaryTitle; - void this._enqueueDictionaryDelete(title); - this._hideUpdatesAvailableButtons(); + void this._enqueueTask({type: 'delete', dictionaryTitle}); + this._hideUpdatesAvailableButton(dictionaryTitle); } /** @@ -884,7 +870,7 @@ export class DictionaryController { if (typeof title !== 'string') { return; } delete modal.node.dataset.dictionaryTitle; - void this._enqueueDictionaryUpdate(title, downloadUrl); + void this._enqueueTask({type: 'update', dictionaryTitle: title, downloadUrl}); this._hideUpdatesAvailableButton(title); } @@ -899,13 +885,6 @@ export class DictionaryController { } } - /** */ - _hideUpdatesAvailableButtons() { - for (const entry of this._dictionaryEntries) { - entry.hideUpdatesAvailableButton(); - } - } - /** * @param {MouseEvent} e */ @@ -994,7 +973,7 @@ export class DictionaryController { /** */ async _checkForUpdates() { - if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isDeletingOrUpdating()) { return; } + if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isTaskQueueRunning) { return; } let hasUpdates; try { this._checkingUpdates = true; @@ -1017,7 +996,7 @@ export class DictionaryController { /** */ async _checkIntegrity() { - if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isDeletingOrUpdating()) { return; } + if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isTaskQueueRunning) { return; } try { this._checkingIntegrity = true; @@ -1093,61 +1072,41 @@ export class DictionaryController { * @param {string} dictionaryTitle * @returns {boolean} */ - isDictionaryInDeleteQueue(dictionaryTitle) { - return this._dictionaryDeleteQueue.includes(dictionaryTitle); - } - - /** - * @param {string} dictionaryTitle - * @returns {boolean} - */ - isDictionaryInUpdateQueue(dictionaryTitle) { - return this._dictionaryUpdateQueue.includes(dictionaryTitle); + isDictionaryInTaskQueue(dictionaryTitle) { + return this._dictionaryTaskQueue.some((task) => task.dictionaryTitle === dictionaryTitle); } /** - * @returns {boolean} + * @param {import('dictionary-controller.js').DictionaryTask} task */ - _isDeletingOrUpdating() { - return this._isDeleting || this._isUpdating; + _enqueueTask(task) { + if (this.isDictionaryInTaskQueue(task.dictionaryTitle)) { return; } + this._dictionaryTaskQueue.push(task); + void this._runTaskQueue(); } - /** - * @param {string} dictionaryTitle - */ - async _enqueueDictionaryDelete(dictionaryTitle) { - if (this.isDictionaryInDeleteQueue(dictionaryTitle)) { return; } - this._dictionaryDeleteQueue.push(dictionaryTitle); - if (this._isDeleting) { return; } - while (this._dictionaryDeleteQueue.length > 0) { - const title = this._dictionaryDeleteQueue[0]; - if (!title) { continue; } - await this._deleteDictionary(title); - void this._dictionaryDeleteQueue.shift(); - } - } - /** - * @param {string} dictionaryTitle - * @param {string|undefined} downloadUrl - */ - async _enqueueDictionaryUpdate(dictionaryTitle, downloadUrl) { - if (this.isDictionaryInUpdateQueue(dictionaryTitle)) { return; } - this._dictionaryUpdateQueue.push(dictionaryTitle); - if (this._isUpdating) { return; } - while (this._dictionaryUpdateQueue.length > 0) { - const title = this._dictionaryUpdateQueue[0]; - if (!title) { continue; } - await this._updateDictionary(title, downloadUrl); - void this._dictionaryUpdateQueue.shift(); + /** */ + async _runTaskQueue() { + if (this._isTaskQueueRunning) { return; } + this._isTaskQueueRunning = true; + while (this._dictionaryTaskQueue.length > 0) { + const task = this._dictionaryTaskQueue[0]; + if (task.type === 'delete') { + await this._deleteDictionary(task.dictionaryTitle); + } else if (task.type === 'update') { + await this._updateDictionary(task.dictionaryTitle, task.downloadUrl); + } + void this._dictionaryTaskQueue.shift(); } + this._isTaskQueueRunning = false; } /** * @param {string} dictionaryTitle */ async _deleteDictionary(dictionaryTitle) { - if (this._isDeleting || this._checkingIntegrity) { return; } + if (this._checkingIntegrity) { return; } const index = this._dictionaryEntries.findIndex((entry) => entry.dictionaryTitle === dictionaryTitle); if (index < 0) { return; } @@ -1160,7 +1119,6 @@ export class DictionaryController { const statusLabels = /** @type {NodeListOf} */ (document.querySelectorAll(`${progressSelector} .progress-status`)); const prevention = this._settingsController.preventPageExit(); try { - this._isDeleting = true; this._setButtonsEnabled(false); /** @@ -1193,7 +1151,6 @@ export class DictionaryController { for (const progress of progressContainers) { progress.hidden = true; } if (statusFooter !== null) { statusFooter.setTaskActive(progressSelector, false); } this._setButtonsEnabled(true); - this._isDeleting = false; this._triggerStorageChanged(); } } @@ -1203,9 +1160,8 @@ export class DictionaryController { * @param {string|undefined} downloadUrl */ async _updateDictionary(dictionaryTitle, downloadUrl) { - if (this._checkingIntegrity || this._checkingUpdates || this._isUpdating || this._dictionaries === null) { return; } + if (this._checkingIntegrity || this._checkingUpdates || this._dictionaries === null) { return; } - this._isUpdating = true; const dictionaryInfo = this._dictionaries.find((entry) => entry.title === dictionaryTitle); if (typeof dictionaryInfo === 'undefined') { throw new Error('Dictionary not found'); } downloadUrl = downloadUrl ?? dictionaryInfo.downloadUrl; @@ -1233,7 +1189,6 @@ export class DictionaryController { this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl, profilesDictionarySettings, onImportDone: resolve}); }); await importPromise; - this._isUpdating = false; } /** diff --git a/types/ext/dictionary-controller.d.ts b/types/ext/dictionary-controller.d.ts new file mode 100644 index 0000000000..a024a7dd19 --- /dev/null +++ b/types/ext/dictionary-controller.d.ts @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023-2024 Yomitan 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 . + */ + +type DictionaryDeleteTask = { + type: 'delete'; + dictionaryTitle: string; +}; + +type DictionaryUpdateTask = { + type: 'update'; + dictionaryTitle: string; + downloadUrl: string | undefined; +}; + +export type DictionaryTask = DictionaryDeleteTask | DictionaryUpdateTask; From 021d794c3cba3e060dae0b67dffe1701a7871ec0 Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Tue, 5 Nov 2024 11:44:15 +0700 Subject: [PATCH 07/15] await database update before running next task --- ext/js/pages/settings/dictionary-controller.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 8b082a9d60..c7408bf940 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -514,6 +514,8 @@ export class DictionaryController { this._dictionaryTaskQueue = []; /** @type {boolean} */ this._isTaskQueueRunning = false; + /** @type {(() => void) | null} */ + this._onDatabaseUpdateDone = null; } /** @type {import('./modal-controller.js').ModalController} */ @@ -744,6 +746,9 @@ export class DictionaryController { this._dictionaries = dictionaries; await this._updateEntries(); + if (this._onDatabaseUpdateDone) { + this._onDatabaseUpdateDone(); + } } /** */ @@ -1097,6 +1102,13 @@ export class DictionaryController { } else if (task.type === 'update') { await this._updateDictionary(task.dictionaryTitle, task.downloadUrl); } + // await database update done before running next task + /** @type {Promise} */ + const databaseUpdatePromise = new Promise((resolve) => { + this._onDatabaseUpdateDone = resolve; + }); + await databaseUpdatePromise; + this._onDatabaseUpdateDone = null; void this._dictionaryTaskQueue.shift(); } this._isTaskQueueRunning = false; From e6848340038fe624540fe8154a526421a180784b Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Tue, 5 Nov 2024 11:44:53 +0700 Subject: [PATCH 08/15] remove cmt --- ext/js/pages/settings/dictionary-controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index c7408bf940..e04da5261c 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -1102,7 +1102,6 @@ export class DictionaryController { } else if (task.type === 'update') { await this._updateDictionary(task.dictionaryTitle, task.downloadUrl); } - // await database update done before running next task /** @type {Promise} */ const databaseUpdatePromise = new Promise((resolve) => { this._onDatabaseUpdateDone = resolve; From 4222e7be919021a4e37dc01abcca38bf38c4aa37 Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Tue, 5 Nov 2024 11:46:53 +0700 Subject: [PATCH 09/15] naming --- ext/js/pages/settings/dictionary-controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index e04da5261c..7248a7cbf5 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -870,13 +870,13 @@ export class DictionaryController { const modal = /** @type {import('./modal.js').Modal} */ (this._updateDictionaryModal); modal.setVisible(false); - const title = modal.node.dataset.dictionaryTitle; + const dictionaryTitle = modal.node.dataset.dictionaryTitle; const downloadUrl = modal.node.dataset.downloadUrl; - if (typeof title !== 'string') { return; } + if (typeof dictionaryTitle !== 'string') { return; } delete modal.node.dataset.dictionaryTitle; - void this._enqueueTask({type: 'update', dictionaryTitle: title, downloadUrl}); - this._hideUpdatesAvailableButton(title); + void this._enqueueTask({type: 'update', dictionaryTitle, downloadUrl}); + this._hideUpdatesAvailableButton(dictionaryTitle); } /** From 7800919004ae60ad7f0e8883bd9032321735719f Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Tue, 5 Nov 2024 14:38:39 +0700 Subject: [PATCH 10/15] Rename var --- ext/js/pages/settings/dictionary-controller.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 7248a7cbf5..15392a628b 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -515,7 +515,7 @@ export class DictionaryController { /** @type {boolean} */ this._isTaskQueueRunning = false; /** @type {(() => void) | null} */ - this._onDatabaseUpdateDone = null; + this._onDictionariesUpdate = null; } /** @type {import('./modal-controller.js').ModalController} */ @@ -746,8 +746,9 @@ export class DictionaryController { this._dictionaries = dictionaries; await this._updateEntries(); - if (this._onDatabaseUpdateDone) { - this._onDatabaseUpdateDone(); + + if (this._onDictionariesUpdate) { + this._onDictionariesUpdate(); } } @@ -1103,11 +1104,11 @@ export class DictionaryController { await this._updateDictionary(task.dictionaryTitle, task.downloadUrl); } /** @type {Promise} */ - const databaseUpdatePromise = new Promise((resolve) => { - this._onDatabaseUpdateDone = resolve; + const dictionariesUpdatePromise = new Promise((resolve) => { + this._onDictionariesUpdate = resolve; }); - await databaseUpdatePromise; - this._onDatabaseUpdateDone = null; + await dictionariesUpdatePromise; + this._onDictionariesUpdate = null; void this._dictionaryTaskQueue.shift(); } this._isTaskQueueRunning = false; From f71557d20c5d0a5caa9efe6f3f59258b60bb9aa6 Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Wed, 6 Nov 2024 13:57:04 +0700 Subject: [PATCH 11/15] move await logic to correct place --- ext/js/pages/settings/dictionary-controller.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 15392a628b..1afcc9a672 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -1103,12 +1103,6 @@ export class DictionaryController { } else if (task.type === 'update') { await this._updateDictionary(task.dictionaryTitle, task.downloadUrl); } - /** @type {Promise} */ - const dictionariesUpdatePromise = new Promise((resolve) => { - this._onDictionariesUpdate = resolve; - }); - await dictionariesUpdatePromise; - this._onDictionariesUpdate = null; void this._dictionaryTaskQueue.shift(); } this._isTaskQueueRunning = false; @@ -1154,8 +1148,14 @@ export class DictionaryController { for (const label of infoLabels) { label.textContent = 'Deleting dictionary...'; } if (statusFooter !== null) { statusFooter.setTaskActive(progressSelector, true); } + /** @type {Promise} */ + const dictionariesUpdatePromise = new Promise((resolve) => { + this._onDictionariesUpdate = resolve; + }); await this._deleteDictionaryInternal(dictionaryTitle, onProgress); await this._deleteDictionarySettings(dictionaryTitle); + await dictionariesUpdatePromise; + this._onDictionariesUpdate = null; } catch (e) { log.error(e); } finally { From 97ba5d75d6ec7e88e076638d44073a924aca58d9 Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Wed, 6 Nov 2024 14:19:15 +0700 Subject: [PATCH 12/15] use deferPromise --- ext/js/pages/settings/dictionary-controller.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 1afcc9a672..8a2c4ef8a1 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -20,6 +20,7 @@ import * as ajvSchemas0 from '../../../lib/validate-schemas.js'; import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {readResponseJson} from '../../core/json.js'; import {log} from '../../core/log.js'; +import {deferPromise} from '../../core/utilities.js'; import {compareRevisions} from '../../dictionary/dictionary-data-util.js'; import {DictionaryWorker} from '../../dictionary/dictionary-worker.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; @@ -887,6 +888,7 @@ export class DictionaryController { for (const entry of this._dictionaryEntries) { if (entry.dictionaryTitle === dictionaryTitle) { entry.hideUpdatesAvailableButton(); + break; } } } @@ -1148,10 +1150,9 @@ export class DictionaryController { for (const label of infoLabels) { label.textContent = 'Deleting dictionary...'; } if (statusFooter !== null) { statusFooter.setTaskActive(progressSelector, true); } - /** @type {Promise} */ - const dictionariesUpdatePromise = new Promise((resolve) => { - this._onDictionariesUpdate = resolve; - }); + /** @type {import('core').DeferredPromiseDetails} */ + const {promise: dictionariesUpdatePromise, resolve} = deferPromise(); + this._onDictionariesUpdate = resolve; await this._deleteDictionaryInternal(dictionaryTitle, onProgress); await this._deleteDictionarySettings(dictionaryTitle); await dictionariesUpdatePromise; @@ -1196,10 +1197,9 @@ export class DictionaryController { } await this._deleteDictionary(dictionaryTitle); - /** @type {Promise} */ - const importPromise = new Promise((resolve) => { - this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl, profilesDictionarySettings, onImportDone: resolve}); - }); + /** @type {import('core').DeferredPromiseDetails} */ + const {promise: importPromise, resolve} = deferPromise(); + this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl, profilesDictionarySettings, onImportDone: resolve}); await importPromise; } From 40fcfe0188cab6c07619507f19396cb734d5479e Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Wed, 6 Nov 2024 14:39:30 +0700 Subject: [PATCH 13/15] move promise to _deleteDictionaryInternal --- ext/js/pages/settings/dictionary-controller.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 8a2c4ef8a1..ab303b4340 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -1105,6 +1105,7 @@ export class DictionaryController { } else if (task.type === 'update') { await this._updateDictionary(task.dictionaryTitle, task.downloadUrl); } + console.log('Task done'); void this._dictionaryTaskQueue.shift(); } this._isTaskQueueRunning = false; @@ -1150,12 +1151,8 @@ export class DictionaryController { for (const label of infoLabels) { label.textContent = 'Deleting dictionary...'; } if (statusFooter !== null) { statusFooter.setTaskActive(progressSelector, true); } - /** @type {import('core').DeferredPromiseDetails} */ - const {promise: dictionariesUpdatePromise, resolve} = deferPromise(); - this._onDictionariesUpdate = resolve; await this._deleteDictionaryInternal(dictionaryTitle, onProgress); await this._deleteDictionarySettings(dictionaryTitle); - await dictionariesUpdatePromise; this._onDictionariesUpdate = null; } catch (e) { log.error(e); @@ -1219,7 +1216,12 @@ export class DictionaryController { */ async _deleteDictionaryInternal(dictionaryTitle, onProgress) { await new DictionaryWorker().deleteDictionary(dictionaryTitle, onProgress); + /** @type {import('core').DeferredPromiseDetails} */ + const {promise: dictionariesUpdatePromise, resolve} = deferPromise(); + this._onDictionariesUpdate = resolve; void this._settingsController.application.api.triggerDatabaseUpdated('dictionary', 'delete'); + await dictionariesUpdatePromise; + this._onDictionariesUpdate = null; } /** From 53662444d3b8f0ebd1bcd8ed8482a4ee8edcbb31 Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Wed, 6 Nov 2024 14:41:26 +0700 Subject: [PATCH 14/15] removeconsole.log --- ext/js/pages/settings/dictionary-controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index ab303b4340..b5c0beacc6 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -1105,7 +1105,6 @@ export class DictionaryController { } else if (task.type === 'update') { await this._updateDictionary(task.dictionaryTitle, task.downloadUrl); } - console.log('Task done'); void this._dictionaryTaskQueue.shift(); } this._isTaskQueueRunning = false; From ea9f9944a575d3ae8ab184ca4031c46cd40d5ea3 Mon Sep 17 00:00:00 2001 From: Khai Truong Date: Wed, 6 Nov 2024 19:57:19 +0700 Subject: [PATCH 15/15] preserve update button after database update --- .../pages/settings/dictionary-controller.js | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index b5c0beacc6..9e2d79e830 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -33,14 +33,17 @@ class DictionaryEntry { * @param {DocumentFragment} fragment * @param {number} index * @param {import('dictionary-importer').Summary} dictionaryInfo + * @param {string | null} updateDownloadUrl */ - constructor(dictionaryController, fragment, index, dictionaryInfo) { + constructor(dictionaryController, fragment, index, dictionaryInfo, updateDownloadUrl) { /** @type {DictionaryController} */ this._dictionaryController = dictionaryController; /** @type {number} */ this._index = index; /** @type {import('dictionary-importer').Summary} */ this._dictionaryInfo = dictionaryInfo; + /** @type {string | null} */ + this._updateDownloadUrl = updateDownloadUrl; /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); /** @type {?import('dictionary-database').DictionaryCountGroup} */ @@ -87,6 +90,7 @@ class DictionaryEntry { this._outdatedButton.hidden = (version >= 3); this._priorityInput.dataset.setting = `dictionaries[${index}].priority`; this._enabledCheckbox.dataset.setting = `dictionaries[${index}].enabled`; + this._showUpdatesAvailableButton(); this._eventListeners.addEventListener(this._enabledCheckbox, 'settingChanged', this._onEnabledChanged.bind(this), false); this._eventListeners.addEventListener(this._menuButton, 'menuOpen', this._onMenuOpen.bind(this), false); this._eventListeners.addEventListener(this._menuButton, 'menuClose', this._onMenuClose.bind(this), false); @@ -153,13 +157,29 @@ class DictionaryEntry { const downloadUrl = latestDownloadUrl ?? currentDownloadUrl; - this._updatesAvailable.dataset.downloadUrl = downloadUrl; - this._updatesAvailable.hidden = false; + this._updateDownloadUrl = downloadUrl; + this._showUpdatesAvailableButton(); return true; } + /** + * @returns {string | null} + */ + get updateDownloadUrl() { + return this._updateDownloadUrl; + } + // Private + /** */ + _showUpdatesAvailableButton() { + if (this._updateDownloadUrl === null || this._dictionaryController.isDictionaryInTaskQueue(this.dictionaryTitle)) { + return; + } + this._updatesAvailable.dataset.downloadUrl = this._updateDownloadUrl; + this._updatesAvailable.hidden = false; + } + /** * @param {import('popup-menu').MenuOpenEvent} e */ @@ -767,7 +787,10 @@ export class DictionaryController { if (dictionaries === null) { return; } this._updateMainDictionarySelectOptions(dictionaries); + /** @type {Map} */ + const dictionaryUpdateDownloadUrlMap = new Map(); for (const entry of this._dictionaryEntries) { + dictionaryUpdateDownloadUrlMap.set(entry.dictionaryTitle, entry.updateDownloadUrl); entry.cleanup(); } this._dictionaryEntries = []; @@ -797,8 +820,9 @@ export class DictionaryController { for (let i = 0, ii = dictionaryOptionsArray.length; i < ii; ++i) { const {name} = dictionaryOptionsArray[i]; const dictionaryInfo = dictionaryInfoMap.get(name); + const updateDownloadUrl = dictionaryUpdateDownloadUrlMap.get(name) ?? null; if (typeof dictionaryInfo === 'undefined') { continue; } - this._createDictionaryEntry(i, dictionaryInfo); + this._createDictionaryEntry(i, dictionaryInfo, updateDownloadUrl); } } @@ -1060,11 +1084,12 @@ export class DictionaryController { /** * @param {number} index * @param {import('dictionary-importer').Summary} dictionaryInfo + * @param {string|null} updateDownloadUrl */ - _createDictionaryEntry(index, dictionaryInfo) { + _createDictionaryEntry(index, dictionaryInfo, updateDownloadUrl) { const fragment = this.instantiateTemplateFragment('dictionary'); - const entry = new DictionaryEntry(this, fragment, index, dictionaryInfo); + const entry = new DictionaryEntry(this, fragment, index, dictionaryInfo, updateDownloadUrl); this._dictionaryEntries.push(entry); entry.prepare();