From b5ca8b988cd89e87aca9ab8472f12f5a46cdcce6 Mon Sep 17 00:00:00 2001 From: hstyi Date: Tue, 15 Jul 2025 09:34:38 +0800 Subject: [PATCH] chore: support plugin incompatibility --- plugins/migration/build.gradle.kts | 2 +- .../MigrationApplicationRunnerExtension.kt | 2 - .../app/termora/plugin/ExtensionProxy.java | 74 ++++++++++++++++++- src/main/kotlin/app/termora/OptionPane.kt | 10 +-- .../kotlin/app/termora/TermoraRestarter.kt | 7 +- src/main/resources/i18n/messages.properties | 9 +-- .../resources/i18n/messages_ru_RU.properties | 3 - .../resources/i18n/messages_zh_CN.properties | 8 +- .../resources/i18n/messages_zh_TW.properties | 8 +- 9 files changed, 85 insertions(+), 38 deletions(-) diff --git a/plugins/migration/build.gradle.kts b/plugins/migration/build.gradle.kts index 4ff7920..cec2b26 100644 --- a/plugins/migration/build.gradle.kts +++ b/plugins/migration/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } -project.version = "0.0.3" +project.version = "0.0.4" dependencies { diff --git a/plugins/migration/src/main/kotlin/app/termora/plugins/migration/MigrationApplicationRunnerExtension.kt b/plugins/migration/src/main/kotlin/app/termora/plugins/migration/MigrationApplicationRunnerExtension.kt index 641498d..c50d49c 100644 --- a/plugins/migration/src/main/kotlin/app/termora/plugins/migration/MigrationApplicationRunnerExtension.kt +++ b/plugins/migration/src/main/kotlin/app/termora/plugins/migration/MigrationApplicationRunnerExtension.kt @@ -187,8 +187,6 @@ class MigrationApplicationRunnerExtension private constructor() : ApplicationRun // 重启 TermoraRestarter.getInstance().scheduleRestart(null, ask = false) - // 退出程序 - Disposer.dispose(TermoraFrameManager.getInstance()) } diff --git a/src/main/java/app/termora/plugin/ExtensionProxy.java b/src/main/java/app/termora/plugin/ExtensionProxy.java index f186e3f..4ff4efd 100644 --- a/src/main/java/app/termora/plugin/ExtensionProxy.java +++ b/src/main/java/app/termora/plugin/ExtensionProxy.java @@ -1,13 +1,23 @@ package app.termora.plugin; +import app.termora.I18n; +import app.termora.OptionPane; +import app.termora.TermoraRestarter; +import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.swing.JOptionPane; import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import java.io.File; +import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.time.Duration; +import java.util.List; record ExtensionProxy(Plugin plugin, Extension extension) implements InvocationHandler { private static final Logger log = LoggerFactory.getLogger(ExtensionProxy.class); @@ -30,14 +40,72 @@ record ExtensionProxy(Plugin plugin, Extension extension) implements InvocationH return method.invoke(extension, args); } catch (InvocationTargetException e) { final Throwable target = e.getTargetException(); - // 尽可能避免抛出致命性错误 - if (target instanceof Error && !(target instanceof VirtualMachineError)) { + + if (target instanceof Error) { if (log.isErrorEnabled()) { log.error("Error Invoking method {}", method.getName(), target); } - throw new IllegalCallerException(target.getMessage(), target); } + + // 二进制不兼容情况 + if (target instanceof LinkageError) { + // 立即卸载 + uninstallPlugin(); + // 重启程序 + restart(); + } + + throw target; } } + + private void restart() { + + if (SwingUtilities.isEventDispatchThread()) { + + OptionPane.INSTANCE.showMessageDialog(null, + I18n.INSTANCE.getString("termora.settings.plugin.not-compatible", plugin.getName()), + UIManager.getString("OptionPane.messageDialogTitle"), + JOptionPane.ERROR_MESSAGE, + Duration.ZERO + ); + + try { + // 立即重启 + TermoraRestarter.Companion.getInstance() + .scheduleRestart(null, false, List.of()); + } catch (Exception e) { + if (log.isErrorEnabled()) { + log.error(e.getMessage(), e); + } + System.exit(1); + } + + } else { + SwingUtilities.invokeLater(this::restart); + } + } + + private void uninstallPlugin() { + final PluginManager pluginManager = PluginManager.Companion.getInstance(); + final PluginDescriptor[] descriptors = pluginManager.getLoadedPluginDescriptor(); + for (PluginDescriptor descriptor : descriptors) { + if (descriptor.getPlugin() != plugin) continue; + final File file = descriptor.getPath(); + if (file == null) continue; + final File uninstalled = FileUtils.getFile(file, "uninstalled"); + try { + if (!uninstalled.createNewFile()) { + if (log.isWarnEnabled()) { + log.error("Create file: {} failed", uninstalled.getAbsolutePath()); + } + } + } catch (IOException e) { + if (log.isErrorEnabled()) { + log.error(e.getMessage(), e); + } + } + } + } } diff --git a/src/main/kotlin/app/termora/OptionPane.kt b/src/main/kotlin/app/termora/OptionPane.kt index 3e940e6..07de26e 100644 --- a/src/main/kotlin/app/termora/OptionPane.kt +++ b/src/main/kotlin/app/termora/OptionPane.kt @@ -16,9 +16,8 @@ import java.awt.Dimension import java.awt.event.WindowAdapter import java.awt.event.WindowEvent import java.io.File +import java.time.Duration import javax.swing.* -import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds object OptionPane { private val coroutineScope = swingCoroutineScope @@ -95,7 +94,7 @@ object OptionPane { message: String, title: String = UIManager.getString("OptionPane.messageDialogTitle"), messageType: Int = JOptionPane.INFORMATION_MESSAGE, - duration: Duration = 0.milliseconds, + duration: Duration = Duration.ZERO, ) { val label = JTextPane() label.contentType = "text/html" @@ -105,11 +104,12 @@ object OptionPane { label.border = BorderFactory.createEmptyBorder() val pane = JOptionPane(label, messageType, JOptionPane.DEFAULT_OPTION) val dialog = initDialog(pane.createDialog(parentComponent, title)) - if (duration.inWholeMilliseconds > 0) { + + if (duration.toMillis() > 0) { dialog.addWindowListener(object : WindowAdapter() { override fun windowOpened(e: WindowEvent) { coroutineScope.launch(Dispatchers.Swing) { - delay(duration.inWholeMilliseconds) + delay(duration.toMillis()) if (dialog.isVisible) { dialog.isVisible = false } diff --git a/src/main/kotlin/app/termora/TermoraRestarter.kt b/src/main/kotlin/app/termora/TermoraRestarter.kt index 2d8fbba..d398077 100644 --- a/src/main/kotlin/app/termora/TermoraRestarter.kt +++ b/src/main/kotlin/app/termora/TermoraRestarter.kt @@ -6,6 +6,7 @@ import org.apache.commons.io.FileUtils import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory import java.awt.Component +import java.awt.event.WindowEvent import java.nio.file.Paths import java.util.concurrent.atomic.AtomicBoolean import javax.swing.JOptionPane @@ -118,9 +119,11 @@ class TermoraRestarter { Restarter.restart(commands.toTypedArray()) } - for (window in TermoraFrameManager.getInstance().getWindows()) { - window.dispose() + val instance = TermoraFrameManager.getInstance() + for (window in instance.getWindows()) { + window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSED)) } + Disposer.dispose(instance) } diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index c3f8f0c..3607f1a 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -73,20 +73,16 @@ termora.settings.about.termora=${termora.title} ({0}) is a cross-pl termora.settings.plugin=Plugins termora.settings.plugin.install=Install -termora.settings.plugin.subscribe=Subscribe -termora.settings.plugin.install-subscription-confirm=Subscription required to install {0} termora.settings.plugin.installed=Installed termora.settings.plugin.marketplace=Marketplace termora.settings.plugin.uninstall=Uninstall -termora.settings.plugin.update=Update -termora.settings.plugin.uninstalled=Uninstalled -termora.settings.plugin.cannot-uninstall=System built-in plugins cannot be uninstalled termora.settings.plugin.uninstall-confirm=Are you sure you want to uninstall {0} ? termora.settings.plugin.uninstall-failed=Uninstall failed termora.settings.plugin.install-failed=Install failed, please try again later termora.settings.plugin.install-from-disk=Install Plugin from Disk... termora.settings.plugin.manage-plugin-repository=Manage Plugin Repository... termora.settings.plugin.install-from-disk-warning={0} plugin will have access to all your data. Are you sure you want to install it? +termora.settings.plugin.not-compatible=The plugin {0} is incompatible with the current version. Please reinstall {0} termora.settings.account=Account termora.settings.account.register=Register @@ -320,12 +316,10 @@ termora.transport.table.owner=Owner # contextmenu termora.transport.table.contextmenu.transfer=Transfer termora.transport.table.contextmenu.edit=${termora.keymgr.edit} -termora.transport.table.contextmenu.edit-command=You must configure the "Edit Command" in "Settings - SFTP" before you can edit the file termora.transport.table.contextmenu.copy-path=Copy Path termora.transport.table.contextmenu.open-in-folder=Open in ${termora.finder} termora.transport.table.contextmenu.rename=${termora.welcome.contextmenu.rename} termora.transport.table.contextmenu.delete=${termora.remove} -termora.transport.table.contextmenu.delete-warning=If the folder is too large, deleting it may take some time termora.transport.table.contextmenu.rm-warning=Using the rm -rf command to delete a file is very dangerous termora.transport.table.contextmenu.change-permissions=Change Permissions... termora.transport.table.contextmenu.refresh=Refresh @@ -434,7 +428,6 @@ termora.visual-window.system-information.used-total=Used / Total termora.visual-window.nvidia-smi=NVIDIA SMI -termora.floating-toolbar.not-supported=This action is not supported termora.floating-toolbar.close-in-current-tab=Close in current tab diff --git a/src/main/resources/i18n/messages_ru_RU.properties b/src/main/resources/i18n/messages_ru_RU.properties index 7d0ff74..14ebbd8 100644 --- a/src/main/resources/i18n/messages_ru_RU.properties +++ b/src/main/resources/i18n/messages_ru_RU.properties @@ -274,12 +274,10 @@ termora.transport.table.owner=Владелец # contextmenu termora.transport.table.contextmenu.transfer=Файлы termora.transport.table.contextmenu.edit=${termora.keymgr.edit} -termora.transport.table.contextmenu.edit-command=Прежде чем редактировать файл, необходимо настроить «Команду редактирования» в разделе «Настройки — SFTP». termora.transport.table.contextmenu.copy-path=Копировать Путь termora.transport.table.contextmenu.open-in-folder=Открыть в {0} termora.transport.table.contextmenu.rename=${termora.welcome.contextmenu.rename} termora.transport.table.contextmenu.delete=${termora.remove} -termora.transport.table.contextmenu.delete-warning=Если папка слишком большая, ее удаление может занять некоторое время. termora.transport.table.contextmenu.rm-warning=Использование команды rm -rf для удаления файла очень опасно. termora.transport.table.contextmenu.change-permissions=Изменить Разрешения... termora.transport.table.contextmenu.refresh=Обновить @@ -377,7 +375,6 @@ termora.visual-window.system-information.used-total=Использовано / termora.visual-window.nvidia-smi=NVIDIA SMI -termora.floating-toolbar.not-supported=Это действие не поддерживается termora.floating-toolbar.close-in-current-tab=Закрыть в текущей вкладке diff --git a/src/main/resources/i18n/messages_zh_CN.properties b/src/main/resources/i18n/messages_zh_CN.properties index 25fc040..c9cd093 100644 --- a/src/main/resources/i18n/messages_zh_CN.properties +++ b/src/main/resources/i18n/messages_zh_CN.properties @@ -89,17 +89,14 @@ termora.settings.plugin=插件 termora.settings.plugin.install=安装 termora.settings.plugin.installed=已安装 termora.settings.plugin.marketplace=市场 -termora.settings.plugin.subscribe=订阅 -termora.settings.plugin.install-subscription-confirm=安装 {0} 需要订阅 termora.settings.plugin.uninstall=卸载 -termora.settings.plugin.uninstalled=已卸载 -termora.settings.plugin.cannot-uninstall=系统内置插件不可以卸载 termora.settings.plugin.uninstall-confirm=你确定要卸载 {0} 吗? termora.settings.plugin.uninstall-failed=卸载失败 termora.settings.plugin.install-from-disk=从磁盘安装插件... termora.settings.plugin.manage-plugin-repository=管理插件仓库... termora.settings.plugin.install-failed=安装失败,请稍后再试 termora.settings.plugin.install-from-disk-warning={0} 插件可以访问你的所有数据,你确定要安装吗? +termora.settings.plugin.not-compatible=插件 {0} 与当前版本不兼容,请重新安装 {0} termora.settings.account=账号 termora.settings.account.login=登录 @@ -317,7 +314,6 @@ termora.transport.table.owner=所有者 # contextmenu termora.transport.table.contextmenu.transfer=传输 termora.transport.table.contextmenu.copy-path=复制路径 -termora.transport.table.contextmenu.edit-command=你必须在 “设置 - SFTP” 中配置 “编辑命令” 后才能编辑文件 termora.transport.table.contextmenu.open-in-folder=在${termora.finder}中打开 termora.transport.table.contextmenu.change-permissions=更改权限... termora.transport.table.contextmenu.refresh=刷新 @@ -327,7 +323,6 @@ termora.transport.table.contextmenu.extract.here=解压到当前目录 termora.transport.table.contextmenu.extract.single=解压到 {0}\\ termora.transport.table.contextmenu.extract.multi=解压到 *\\* termora.transport.table.contextmenu.new.file=${termora.transport.table.contextmenu.new}文件 -termora.transport.table.contextmenu.delete-warning=如果文件夹太大,删除可能需要耗费一定时间 termora.transport.table.contextmenu.rm-warning=使用 rm -rf 命令删除文件存在很大风险 termora.transport.sftp=传输 @@ -426,7 +421,6 @@ termora.visual-window.system-information.swap=交换 termora.visual-window.system-information.filesystem=文件系统 termora.visual-window.system-information.used-total=使用 / 大小 -termora.floating-toolbar.not-supported=不允许此操作 termora.floating-toolbar.close-in-current-tab=在当前标签页关闭 diff --git a/src/main/resources/i18n/messages_zh_TW.properties b/src/main/resources/i18n/messages_zh_TW.properties index 54a60b0..0c01f78 100644 --- a/src/main/resources/i18n/messages_zh_TW.properties +++ b/src/main/resources/i18n/messages_zh_TW.properties @@ -99,17 +99,14 @@ termora.settings.plugin=插件 termora.settings.plugin.install=安裝 termora.settings.plugin.installed=已安裝 termora.settings.plugin.marketplace=市場 -termora.settings.plugin.subscribe=訂閱 -termora.settings.plugin.install-subscription-confirm=安裝 {0} 需要訂閱 termora.settings.plugin.uninstall=解除安裝 -termora.settings.plugin.uninstalled=已解除安裝 -termora.settings.plugin.cannot-uninstall=系統內建插件無法卸載 termora.settings.plugin.uninstall-confirm=你確定要卸載 {0} 嗎? termora.settings.plugin.uninstall-failed=解除安裝失敗 termora.settings.plugin.install-from-disk=從磁碟安裝插件... termora.settings.plugin.manage-plugin-repository=管理插件倉庫... termora.settings.plugin.install-failed=安裝失敗,請稍後再試 termora.settings.plugin.install-from-disk-warning={0} 插件可以存取你的所有數據,你確定要安裝嗎? +termora.settings.plugin.not-compatible=插件 {0} 與目前版本不相容,請重新安裝 {0} termora.settings.account=帳號 termora.settings.account.login=登入 @@ -312,7 +309,6 @@ termora.transport.table.owner=所有者 # contextmenu termora.transport.table.contextmenu.transfer=傳輸 termora.transport.table.contextmenu.copy-path=複製路徑 -termora.transport.table.contextmenu.edit-command=你必須在 “設定 - SFTP” 中設定 “編輯指令” 後才能編輯文件 termora.transport.table.contextmenu.open-in-folder=在${termora.finder}中打開 termora.transport.table.contextmenu.change-permissions=更改權限... termora.transport.table.contextmenu.refresh=刷新 @@ -322,7 +318,6 @@ termora.transport.table.contextmenu.extract.here=解壓縮到目前目錄 termora.transport.table.contextmenu.extract.single=解壓縮到 {0}\\ termora.transport.table.contextmenu.extract.multi=解壓縮到 *\\* termora.transport.table.contextmenu.new.file=${termora.transport.table.contextmenu.new}文件 -termora.transport.table.contextmenu.delete-warning=如果資料夾太大,刪除可能需要耗費一定時間 termora.transport.table.contextmenu.rm-warning=使用 rm -rf 命令刪除資料存在很大風險 termora.transport.sftp=傳輸 @@ -413,7 +408,6 @@ termora.visual-window.system-information.swap=交換 termora.visual-window.system-information.filesystem=檔案系統 termora.visual-window.system-information.used-total=使用 / 大小 -termora.floating-toolbar.not-supported=不允許此操作 termora.floating-toolbar.close-in-current-tab=在目前標籤頁關閉 # zmodem