chore: support plugin incompatibility

This commit is contained in:
hstyi
2025-07-15 09:34:38 +08:00
committed by GitHub
parent 89b22ba984
commit b5ca8b988c
9 changed files with 85 additions and 38 deletions

View File

@@ -3,7 +3,7 @@ plugins {
} }
project.version = "0.0.3" project.version = "0.0.4"
dependencies { dependencies {

View File

@@ -187,8 +187,6 @@ class MigrationApplicationRunnerExtension private constructor() : ApplicationRun
// 重启 // 重启
TermoraRestarter.getInstance().scheduleRestart(null, ask = false) TermoraRestarter.getInstance().scheduleRestart(null, ask = false)
// 退出程序
Disposer.dispose(TermoraFrameManager.getInstance())
} }

View File

@@ -1,13 +1,23 @@
package app.termora.plugin; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities; 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.InvocationHandler;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.time.Duration;
import java.util.List;
record ExtensionProxy(Plugin plugin, Extension extension) implements InvocationHandler { record ExtensionProxy(Plugin plugin, Extension extension) implements InvocationHandler {
private static final Logger log = LoggerFactory.getLogger(ExtensionProxy.class); 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); return method.invoke(extension, args);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
final Throwable target = e.getTargetException(); final Throwable target = e.getTargetException();
// 尽可能避免抛出致命性错误
if (target instanceof Error && !(target instanceof VirtualMachineError)) { if (target instanceof Error) {
if (log.isErrorEnabled()) { if (log.isErrorEnabled()) {
log.error("Error Invoking method {}", method.getName(), target); log.error("Error Invoking method {}", method.getName(), target);
} }
throw new IllegalCallerException(target.getMessage(), target);
} }
// 二进制不兼容情况
if (target instanceof LinkageError) {
// 立即卸载
uninstallPlugin();
// 重启程序
restart();
}
throw target; 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);
}
}
}
}
} }

View File

@@ -16,9 +16,8 @@ import java.awt.Dimension
import java.awt.event.WindowAdapter import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent import java.awt.event.WindowEvent
import java.io.File import java.io.File
import java.time.Duration
import javax.swing.* import javax.swing.*
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
object OptionPane { object OptionPane {
private val coroutineScope = swingCoroutineScope private val coroutineScope = swingCoroutineScope
@@ -95,7 +94,7 @@ object OptionPane {
message: String, message: String,
title: String = UIManager.getString("OptionPane.messageDialogTitle"), title: String = UIManager.getString("OptionPane.messageDialogTitle"),
messageType: Int = JOptionPane.INFORMATION_MESSAGE, messageType: Int = JOptionPane.INFORMATION_MESSAGE,
duration: Duration = 0.milliseconds, duration: Duration = Duration.ZERO,
) { ) {
val label = JTextPane() val label = JTextPane()
label.contentType = "text/html" label.contentType = "text/html"
@@ -105,11 +104,12 @@ object OptionPane {
label.border = BorderFactory.createEmptyBorder() label.border = BorderFactory.createEmptyBorder()
val pane = JOptionPane(label, messageType, JOptionPane.DEFAULT_OPTION) val pane = JOptionPane(label, messageType, JOptionPane.DEFAULT_OPTION)
val dialog = initDialog(pane.createDialog(parentComponent, title)) val dialog = initDialog(pane.createDialog(parentComponent, title))
if (duration.inWholeMilliseconds > 0) {
if (duration.toMillis() > 0) {
dialog.addWindowListener(object : WindowAdapter() { dialog.addWindowListener(object : WindowAdapter() {
override fun windowOpened(e: WindowEvent) { override fun windowOpened(e: WindowEvent) {
coroutineScope.launch(Dispatchers.Swing) { coroutineScope.launch(Dispatchers.Swing) {
delay(duration.inWholeMilliseconds) delay(duration.toMillis())
if (dialog.isVisible) { if (dialog.isVisible) {
dialog.isVisible = false dialog.isVisible = false
} }

View File

@@ -6,6 +6,7 @@ import org.apache.commons.io.FileUtils
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.awt.Component import java.awt.Component
import java.awt.event.WindowEvent
import java.nio.file.Paths import java.nio.file.Paths
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.JOptionPane import javax.swing.JOptionPane
@@ -118,9 +119,11 @@ class TermoraRestarter {
Restarter.restart(commands.toTypedArray()) Restarter.restart(commands.toTypedArray())
} }
for (window in TermoraFrameManager.getInstance().getWindows()) { val instance = TermoraFrameManager.getInstance()
window.dispose() for (window in instance.getWindows()) {
window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSED))
} }
Disposer.dispose(instance)
} }

View File

@@ -73,20 +73,16 @@ termora.settings.about.termora=<html><b>${termora.title}</b> ({0}) is a cross-pl
termora.settings.plugin=Plugins termora.settings.plugin=Plugins
termora.settings.plugin.install=Install termora.settings.plugin.install=Install
termora.settings.plugin.subscribe=Subscribe
termora.settings.plugin.install-subscription-confirm=Subscription required to install <b>{0}</b>
termora.settings.plugin.installed=Installed termora.settings.plugin.installed=Installed
termora.settings.plugin.marketplace=Marketplace termora.settings.plugin.marketplace=Marketplace
termora.settings.plugin.uninstall=Uninstall 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 <b>{0}</b> ? termora.settings.plugin.uninstall-confirm=Are you sure you want to uninstall <b>{0}</b> ?
termora.settings.plugin.uninstall-failed=Uninstall failed termora.settings.plugin.uninstall-failed=Uninstall failed
termora.settings.plugin.install-failed=Install failed, please try again later termora.settings.plugin.install-failed=Install failed, please try again later
termora.settings.plugin.install-from-disk=Install Plugin from Disk... termora.settings.plugin.install-from-disk=Install Plugin from Disk...
termora.settings.plugin.manage-plugin-repository=Manage Plugin Repository... termora.settings.plugin.manage-plugin-repository=Manage Plugin Repository...
termora.settings.plugin.install-from-disk-warning=<b>{0}</b> plugin will have access to all your data. Are you sure you want to install it? termora.settings.plugin.install-from-disk-warning=<b>{0}</b> plugin will have access to all your data. Are you sure you want to install it?
termora.settings.plugin.not-compatible=The plugin <b>{0}</b> is incompatible with the current version. Please reinstall <b>{0}</b>
termora.settings.account=Account termora.settings.account=Account
termora.settings.account.register=Register termora.settings.account.register=Register
@@ -320,12 +316,10 @@ termora.transport.table.owner=Owner
# contextmenu # contextmenu
termora.transport.table.contextmenu.transfer=Transfer termora.transport.table.contextmenu.transfer=Transfer
termora.transport.table.contextmenu.edit=${termora.keymgr.edit} 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.copy-path=Copy Path
termora.transport.table.contextmenu.open-in-folder=Open in ${termora.finder} termora.transport.table.contextmenu.open-in-folder=Open in ${termora.finder}
termora.transport.table.contextmenu.rename=${termora.welcome.contextmenu.rename} termora.transport.table.contextmenu.rename=${termora.welcome.contextmenu.rename}
termora.transport.table.contextmenu.delete=${termora.remove} 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.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.change-permissions=Change Permissions...
termora.transport.table.contextmenu.refresh=Refresh 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.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 termora.floating-toolbar.close-in-current-tab=Close in current tab

View File

@@ -274,12 +274,10 @@ termora.transport.table.owner=Владелец
# contextmenu # contextmenu
termora.transport.table.contextmenu.transfer=Файлы termora.transport.table.contextmenu.transfer=Файлы
termora.transport.table.contextmenu.edit=${termora.keymgr.edit} 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.copy-path=Копировать Путь
termora.transport.table.contextmenu.open-in-folder=Открыть в {0} termora.transport.table.contextmenu.open-in-folder=Открыть в {0}
termora.transport.table.contextmenu.rename=${termora.welcome.contextmenu.rename} termora.transport.table.contextmenu.rename=${termora.welcome.contextmenu.rename}
termora.transport.table.contextmenu.delete=${termora.remove} 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.rm-warning=Использование команды rm -rf для удаления файла очень опасно.
termora.transport.table.contextmenu.change-permissions=Изменить Разрешения... termora.transport.table.contextmenu.change-permissions=Изменить Разрешения...
termora.transport.table.contextmenu.refresh=Обновить termora.transport.table.contextmenu.refresh=Обновить
@@ -377,7 +375,6 @@ termora.visual-window.system-information.used-total=Использовано /
termora.visual-window.nvidia-smi=NVIDIA SMI termora.visual-window.nvidia-smi=NVIDIA SMI
termora.floating-toolbar.not-supported=Это действие не поддерживается
termora.floating-toolbar.close-in-current-tab=Закрыть в текущей вкладке termora.floating-toolbar.close-in-current-tab=Закрыть в текущей вкладке

View File

@@ -89,17 +89,14 @@ termora.settings.plugin=插件
termora.settings.plugin.install=安装 termora.settings.plugin.install=安装
termora.settings.plugin.installed=已安装 termora.settings.plugin.installed=已安装
termora.settings.plugin.marketplace=市场 termora.settings.plugin.marketplace=市场
termora.settings.plugin.subscribe=订阅
termora.settings.plugin.install-subscription-confirm=安装 <b>{0}</b> 需要订阅
termora.settings.plugin.uninstall=卸载 termora.settings.plugin.uninstall=卸载
termora.settings.plugin.uninstalled=已卸载
termora.settings.plugin.cannot-uninstall=系统内置插件不可以卸载
termora.settings.plugin.uninstall-confirm=你确定要卸载 <b>{0}</b> 吗? termora.settings.plugin.uninstall-confirm=你确定要卸载 <b>{0}</b> 吗?
termora.settings.plugin.uninstall-failed=卸载失败 termora.settings.plugin.uninstall-failed=卸载失败
termora.settings.plugin.install-from-disk=从磁盘安装插件... termora.settings.plugin.install-from-disk=从磁盘安装插件...
termora.settings.plugin.manage-plugin-repository=管理插件仓库... termora.settings.plugin.manage-plugin-repository=管理插件仓库...
termora.settings.plugin.install-failed=安装失败,请稍后再试 termora.settings.plugin.install-failed=安装失败,请稍后再试
termora.settings.plugin.install-from-disk-warning=<b>{0}</b> 插件可以访问你的所有数据,你确定要安装吗? termora.settings.plugin.install-from-disk-warning=<b>{0}</b> 插件可以访问你的所有数据,你确定要安装吗?
termora.settings.plugin.not-compatible=插件 <b>{0}</b> 与当前版本不兼容,请重新安装 <b>{0}</b>
termora.settings.account=账号 termora.settings.account=账号
termora.settings.account.login=登录 termora.settings.account.login=登录
@@ -317,7 +314,6 @@ termora.transport.table.owner=所有者
# contextmenu # contextmenu
termora.transport.table.contextmenu.transfer=传输 termora.transport.table.contextmenu.transfer=传输
termora.transport.table.contextmenu.copy-path=复制路径 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.open-in-folder=在${termora.finder}中打开
termora.transport.table.contextmenu.change-permissions=更改权限... termora.transport.table.contextmenu.change-permissions=更改权限...
termora.transport.table.contextmenu.refresh=刷新 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.single=解压到 {0}\\
termora.transport.table.contextmenu.extract.multi=解压到 *\\* termora.transport.table.contextmenu.extract.multi=解压到 *\\*
termora.transport.table.contextmenu.new.file=${termora.transport.table.contextmenu.new}文件 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.table.contextmenu.rm-warning=使用 rm -rf 命令删除文件存在很大风险
termora.transport.sftp=传输 termora.transport.sftp=传输
@@ -426,7 +421,6 @@ termora.visual-window.system-information.swap=交换
termora.visual-window.system-information.filesystem=文件系统 termora.visual-window.system-information.filesystem=文件系统
termora.visual-window.system-information.used-total=使用 / 大小 termora.visual-window.system-information.used-total=使用 / 大小
termora.floating-toolbar.not-supported=不允许此操作
termora.floating-toolbar.close-in-current-tab=在当前标签页关闭 termora.floating-toolbar.close-in-current-tab=在当前标签页关闭

View File

@@ -99,17 +99,14 @@ termora.settings.plugin=插件
termora.settings.plugin.install=安裝 termora.settings.plugin.install=安裝
termora.settings.plugin.installed=已安裝 termora.settings.plugin.installed=已安裝
termora.settings.plugin.marketplace=市場 termora.settings.plugin.marketplace=市場
termora.settings.plugin.subscribe=訂閱
termora.settings.plugin.install-subscription-confirm=安裝 <b>{0}</b> 需要訂閱
termora.settings.plugin.uninstall=解除安裝 termora.settings.plugin.uninstall=解除安裝
termora.settings.plugin.uninstalled=已解除安裝
termora.settings.plugin.cannot-uninstall=系統內建插件無法卸載
termora.settings.plugin.uninstall-confirm=你確定要卸載 <b>{0}</b> 嗎? termora.settings.plugin.uninstall-confirm=你確定要卸載 <b>{0}</b> 嗎?
termora.settings.plugin.uninstall-failed=解除安裝失敗 termora.settings.plugin.uninstall-failed=解除安裝失敗
termora.settings.plugin.install-from-disk=從磁碟安裝插件... termora.settings.plugin.install-from-disk=從磁碟安裝插件...
termora.settings.plugin.manage-plugin-repository=管理插件倉庫... termora.settings.plugin.manage-plugin-repository=管理插件倉庫...
termora.settings.plugin.install-failed=安裝失敗,請稍後再試 termora.settings.plugin.install-failed=安裝失敗,請稍後再試
termora.settings.plugin.install-from-disk-warning=<b>{0}</b> 插件可以存取你的所有數據,你確定要安裝嗎? termora.settings.plugin.install-from-disk-warning=<b>{0}</b> 插件可以存取你的所有數據,你確定要安裝嗎?
termora.settings.plugin.not-compatible=插件 <b>{0}</b> 與目前版本不相容,請重新安裝 <b>{0}</b>
termora.settings.account=帳號 termora.settings.account=帳號
termora.settings.account.login=登入 termora.settings.account.login=登入
@@ -312,7 +309,6 @@ termora.transport.table.owner=所有者
# contextmenu # contextmenu
termora.transport.table.contextmenu.transfer=傳輸 termora.transport.table.contextmenu.transfer=傳輸
termora.transport.table.contextmenu.copy-path=複製路徑 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.open-in-folder=在${termora.finder}中打開
termora.transport.table.contextmenu.change-permissions=更改權限... termora.transport.table.contextmenu.change-permissions=更改權限...
termora.transport.table.contextmenu.refresh=刷新 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.single=解壓縮到 {0}\\
termora.transport.table.contextmenu.extract.multi=解壓縮到 *\\* termora.transport.table.contextmenu.extract.multi=解壓縮到 *\\*
termora.transport.table.contextmenu.new.file=${termora.transport.table.contextmenu.new}文件 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.table.contextmenu.rm-warning=使用 rm -rf 命令刪除資料存在很大風險
termora.transport.sftp=傳輸 termora.transport.sftp=傳輸
@@ -413,7 +408,6 @@ termora.visual-window.system-information.swap=交換
termora.visual-window.system-information.filesystem=檔案系統 termora.visual-window.system-information.filesystem=檔案系統
termora.visual-window.system-information.used-total=使用 / 大小 termora.visual-window.system-information.used-total=使用 / 大小
termora.floating-toolbar.not-supported=不允許此操作
termora.floating-toolbar.close-in-current-tab=在目前標籤頁關閉 termora.floating-toolbar.close-in-current-tab=在目前標籤頁關閉
# zmodem # zmodem