diff options
-rw-r--r-- | ext/bg/js/backend.js | 50 | ||||
-rw-r--r-- | ext/bg/js/mecab.js | 33 | ||||
-rw-r--r-- | ext/bg/js/settings2/mecab-controller.js | 67 | ||||
-rw-r--r-- | ext/bg/js/settings2/settings-main.js | 4 | ||||
-rw-r--r-- | ext/bg/settings2.html | 7 | ||||
-rw-r--r-- | ext/mixed/js/api.js | 4 |
6 files changed, 163 insertions, 2 deletions
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 67b17cc9..f1983cb3 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -122,7 +122,8 @@ class Backend { ['setAllSettings', {async: true, contentScript: false, handler: this._onApiSetAllSettings.bind(this)}], ['getOrCreateSearchPopup', {async: true, contentScript: true, handler: this._onApiGetOrCreateSearchPopup.bind(this)}], ['isTabSearchPopup', {async: true, contentScript: true, handler: this._onApiIsTabSearchPopup.bind(this)}], - ['triggerDatabaseUpdated', {async: false, contentScript: true, handler: this._onApiTriggerDatabaseUpdated.bind(this)}] + ['triggerDatabaseUpdated', {async: false, contentScript: true, handler: this._onApiTriggerDatabaseUpdated.bind(this)}], + ['testMecab', {async: true, contentScript: true, handler: this._onApiTestMecab.bind(this)}] ]); this._messageHandlersWithProgress = new Map([ ]); @@ -676,6 +677,42 @@ class Backend { this._triggerDatabaseUpdated(type, cause); } + async _onApiTestMecab() { + if (!this._mecab.isEnabled()) { + throw new Error('MeCab not enabled'); + } + + let permissionsOkay = false; + try { + permissionsOkay = await this._hasPermissions({permissions: ['nativeMessaging']}); + } catch (e) { + // NOP + } + if (!permissionsOkay) { + throw new Error('Insufficient permissions'); + } + + const disconnect = !this._mecab.isConnected(); + try { + const version = await this._mecab.getVersion(); + if (version === null) { + throw new Error('Could not connect to native MeCab component'); + } + + const localVersion = this._mecab.getLocalVersion(); + if (version !== localVersion) { + throw new Error(`MeCab component version not supported: ${version}`); + } + } finally { + // Disconnect if the connection was previously disconnected + if (disconnect && this._mecab.isEnabled() && this._mecab.isActive()) { + this._mecab.disconnect(); + } + } + + return true; + } + // Command handlers async _onCommandOpenSearchPage(params) { @@ -1904,4 +1941,15 @@ class Backend { }); }); } + + _hasPermissions(permissions) { + return new Promise((resolve, reject) => chrome.permissions.contains(permissions, (result) => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(result); + } + })); + } } diff --git a/ext/bg/js/mecab.js b/ext/bg/js/mecab.js index ef5d6821..4eff2927 100644 --- a/ext/bg/js/mecab.js +++ b/ext/bg/js/mecab.js @@ -54,6 +54,39 @@ class Mecab { } /** + * Disconnects the current port, but does not disable future connections. + */ + disconnect() { + if (this._port !== null) { + this._clearPort(); + } + } + + /** + * Returns whether or not the connection to the native application is active. + * @returns `true` if the connection is active, `false` otherwise. + */ + isConnected() { + return (this._port !== null); + } + + /** + * Returns whether or not any invocation is currently active. + * @returns `true` if an invocation is active, `false` otherwise. + */ + isActive() { + return (this._invocations.size > 0); + } + + /** + * Gets the local API version being used. + * @returns An integer representing the API version that Yomichan uses. + */ + getLocalVersion() { + return this._version; + } + + /** * Gets the version of the MeCab component. * @returns The version of the MeCab component, or `null` if the component was not found. */ diff --git a/ext/bg/js/settings2/mecab-controller.js b/ext/bg/js/settings2/mecab-controller.js new file mode 100644 index 00000000..ff2a4a66 --- /dev/null +++ b/ext/bg/js/settings2/mecab-controller.js @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 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 <https://www.gnu.org/licenses/>. + */ + +/* global + * api + */ + +class MecabController { + constructor(settingsController) { + this._settingsController = settingsController; + this._testButton = null; + this._resultsContainer = null; + this._testActive = false; + } + + prepare() { + this._testButton = document.querySelector('#test-mecab-button'); + this._resultsContainer = document.querySelector('#test-mecab-results'); + + this._testButton.addEventListener('click', this._onTestButtonClick.bind(this), false); + } + + // Private + + _onTestButtonClick(e) { + e.preventDefault(); + this._testMecab(); + } + + async _testMecab() { + if (this._testActive) { return; } + + try { + this._testActive = true; + this._testButton.disabled = true; + this._resultsContainer.textContent = ''; + this._resultsContainer.hidden = true; + await api.testMecab(); + this._setStatus('Connection was successful', false); + } catch (e) { + this._setStatus(e.message, true); + } finally { + this._testActive = false; + this._testButton.disabled = false; + } + } + + _setStatus(message, isError) { + this._resultsContainer.textContent = message; + this._resultsContainer.hidden = false; + this._resultsContainer.classList.toggle('danger-text', isError); + } +} diff --git a/ext/bg/js/settings2/settings-main.js b/ext/bg/js/settings2/settings-main.js index f4a38d85..24248110 100644 --- a/ext/bg/js/settings2/settings-main.js +++ b/ext/bg/js/settings2/settings-main.js @@ -26,6 +26,7 @@ * ExtensionKeyboardShortcutController * GenericSettingController * KeyboardShortcutController + * MecabController * ModalController * NestedPopupsController * PermissionsToggleController @@ -140,6 +141,9 @@ async function setupGenericSettingsController(genericSettingController) { const popupWindowController = new PopupWindowController(); popupWindowController.prepare(); + const mecabController = new MecabController(); + mecabController.prepare(); + await Promise.all(preparePromises); document.documentElement.dataset.loaded = 'true'; diff --git a/ext/bg/settings2.html b/ext/bg/settings2.html index 41293a6e..b772ece1 100644 --- a/ext/bg/settings2.html +++ b/ext/bg/settings2.html @@ -1166,7 +1166,11 @@ In order for Yomichan to use it, both MeCab and a native messaging component must be installed. A setup guide can be found <a href="https://github.com/siikamiika/yomichan-mecab-installer/blob/master/README.md" target="_blank" rel="noopener noreferrer">here</a>. </p> - <p> + <div class="margin-above flex-row-nowrap"> + <button id="test-mecab-button">Test</button> + <div id="test-mecab-results" class="flex-margin-left" hidden></div> + </div> + <p class="margin-above"> <a class="more-toggle" data-parent-distance="3">Less…</a> </p> </div> @@ -3241,6 +3245,7 @@ <script src="/bg/js/settings2/extension-keyboard-shortcuts-controller.js"></script> <script src="/bg/js/settings2/keyboard-shortcuts-controller.js"></script> +<script src="/bg/js/settings2/mecab-controller.js"></script> <script src="/bg/js/settings2/nested-popups-controller.js"></script> <script src="/bg/js/settings2/popup-window-controller.js"></script> <script src="/bg/js/settings2/secondary-search-dictionary-controller.js"></script> diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index fc765063..d37b091a 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -193,6 +193,10 @@ const api = (() => { return this._invoke('triggerDatabaseUpdated', {type, cause}); } + testMecab() { + return this._invoke('testMecab', {}); + } + // Utilities _createActionPort(timeout=5000) { |