diff --git a/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java b/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java index 164792e6a..38e8a49c9 100644 --- a/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java +++ b/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.time.Duration; import java.util.ArrayList; import java.util.Comparator; @@ -61,6 +62,7 @@ import run.halo.app.extension.ConfigMap; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.router.IListRequest.QueryListRequest; import run.halo.app.infra.SystemVersionSupplier; +import run.halo.app.infra.exception.PluginInstallationException; import run.halo.app.infra.exception.UnsatisfiedAttributeValueException; import run.halo.app.infra.utils.FileUtils; import run.halo.app.infra.utils.VersionUtils; @@ -455,25 +457,25 @@ public class PluginEndpoint implements CustomEndpoint { // Disable auto enable during installation plugin.getSpec().setEnabled(false); return client.fetch(Plugin.class, plugin.getMetadata().getName()) - .switchIfEmpty(Mono.defer(() -> client.create(plugin))) + .doOnNext(oldPlugin -> { + String pluginName = oldPlugin.getMetadata().getName(); + throw new PluginInstallationException( + "Plugin [" + pluginName + "] already installed", + "problemDetail.plugin.install.alreadyInstalled", + new Object[] {pluginName}); + }) + .then(client.create(plugin)) .publishOn(Schedulers.boundedElastic()) - .map(created -> { + .doOnNext(created -> { String fileName = created.generateFileName(); var pluginRoot = Paths.get(pluginProperties.getPluginsRoot()); createDirectoriesIfNotExists(pluginRoot); Path pluginFilePath = pluginRoot.resolve(fileName); - if (Files.exists(pluginFilePath)) { - throw new IllegalArgumentException( - "Plugin already installed : " + pluginFilePath); - } - FileUtils.copy(tempJarFilePath, pluginFilePath); - return created; + // move the plugin jar file to the plugin root + // replace the old plugin jar file if exists + FileUtils.copy(tempJarFilePath, pluginFilePath, + StandardCopyOption.REPLACE_EXISTING); }) - .onErrorResume( - error -> client.fetch(Plugin.class, plugin.getMetadata().getName()) - .flatMap(client::delete) - .then(Mono.error(error)) - ) .doFinally(signalType -> { try { Files.deleteIfExists(tempJarFilePath); diff --git a/src/main/java/run/halo/app/infra/exception/PluginInstallationException.java b/src/main/java/run/halo/app/infra/exception/PluginInstallationException.java new file mode 100644 index 000000000..e6100f5c1 --- /dev/null +++ b/src/main/java/run/halo/app/infra/exception/PluginInstallationException.java @@ -0,0 +1,19 @@ +package run.halo.app.infra.exception; + +import jakarta.validation.constraints.Null; +import org.springframework.lang.Nullable; +import org.springframework.web.server.ServerWebInputException; + +/** + * {@link ServerWebInputException} subclass that indicates plugin installation failure. + * + * @author guqing + * @since 2.0.0 + */ +public class PluginInstallationException extends ServerWebInputException { + + public PluginInstallationException(String reason, @Nullable String messageDetailCode, + @Null Object[] messageDetailArguments) { + super(reason, null, null, messageDetailCode, messageDetailArguments); + } +} diff --git a/src/main/resources/config/i18n/messages.properties b/src/main/resources/config/i18n/messages.properties index 89463b3d7..f0dd3280c 100644 --- a/src/main/resources/config/i18n/messages.properties +++ b/src/main/resources/config/i18n/messages.properties @@ -14,6 +14,7 @@ problemDetail.title.run.halo.app.infra.exception.AccessDeniedException=Access De problemDetail.title.reactor.core.Exceptions.RetryExhaustedException=Retry Exhausted problemDetail.title.run.halo.app.infra.exception.ThemeInstallationException=Theme Install Error problemDetail.title.run.halo.app.infra.exception.ThemeUpgradeException=Theme Upgrade Error +problemDetail.title.run.halo.app.infra.exception.PluginInstallationException=Plugin Install Error # Detail definitions problemDetail.org.springframework.web.server.UnsupportedMediaTypeStatusException=Content type {0} is not supported. Supported media types: {1}. @@ -38,3 +39,4 @@ problemDetail.theme.version.unsatisfied.requires=The theme requires a minimum sy problemDetail.directoryTraversal=Directory traversal detected. Base path is {0}, but real path is {1}. problemDetail.plugin.version.unsatisfied.requires=Plugin requires a minimum system version of {0}, but the current version is {1}. +problemDetail.plugin.install.alreadyInstalled=Plugin {0} already installed. \ No newline at end of file diff --git a/src/main/resources/config/i18n/messages_zh.properties b/src/main/resources/config/i18n/messages_zh.properties index 2d7c204e3..a35ca8f25 100644 --- a/src/main/resources/config/i18n/messages_zh.properties +++ b/src/main/resources/config/i18n/messages_zh.properties @@ -1,9 +1,11 @@ problemDetail.title.org.springframework.web.server.ServerWebInputException=请求参数有误 problemDetail.title.run.halo.app.infra.exception.UnsatisfiedAttributeValueException=请求参数属性值不满足要求 +problemDetail.title.run.halo.app.infra.exception.PluginInstallationException=插件安装失败 problemDetail.title.run.halo.app.infra.exception.AttachmentAlreadyExistsException=附件已存在 problemDetail.run.halo.app.infra.exception.AttachmentAlreadyExistsException=文件 {0} 已存在,建议更名后重试。 problemDetail.plugin.version.unsatisfied.requires=插件要求一个最小的系统版本为 {0}, 但当前版本为 {1}。 +problemDetail.plugin.install.alreadyInstalled=插件 {0} 已经被安装。 problemDetail.theme.version.unsatisfied.requires=主题要求一个最小的系统版本为 {0}, 但当前版本为 {1}。 \ No newline at end of file