diff --git a/src/main/java/run/halo/app/exception/ThemeNotSupportException.java b/src/main/java/run/halo/app/exception/ThemeNotSupportException.java new file mode 100644 index 000000000..1eabe73ad --- /dev/null +++ b/src/main/java/run/halo/app/exception/ThemeNotSupportException.java @@ -0,0 +1,18 @@ +package run.halo.app.exception; + +/** + * Theme not support exception. + * + * @author ryanwang + * @date 2020-02-03 + */ +public class ThemeNotSupportException extends BadRequestException { + + public ThemeNotSupportException(String message) { + super(message); + } + + public ThemeNotSupportException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/run/halo/app/handler/theme/config/support/ThemeProperty.java b/src/main/java/run/halo/app/handler/theme/config/support/ThemeProperty.java index 784e97cef..eaf5722b8 100644 --- a/src/main/java/run/halo/app/handler/theme/config/support/ThemeProperty.java +++ b/src/main/java/run/halo/app/handler/theme/config/support/ThemeProperty.java @@ -54,6 +54,11 @@ public class ThemeProperty { */ private String version; + /** + * Require halo version. + */ + private String require; + /** * Theme author. */ diff --git a/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java b/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java index b5c91cd10..32c9f92a5 100644 --- a/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java @@ -4,8 +4,13 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.PullResult; +import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; import org.springframework.context.ApplicationEventPublisher; @@ -31,10 +36,7 @@ import run.halo.app.model.support.HaloConst; import run.halo.app.model.support.ThemeFile; import run.halo.app.service.OptionService; import run.halo.app.service.ThemeService; -import run.halo.app.utils.FileUtils; -import run.halo.app.utils.FilenameUtils; -import run.halo.app.utils.GitUtils; -import run.halo.app.utils.HaloUtils; +import run.halo.app.utils.*; import java.io.IOException; import java.net.URISyntaxException; @@ -477,6 +479,11 @@ public class ThemeServiceImpl implements ThemeService { throw new AlreadyExistsException("当前安装的主题已存在"); } + // Not support current halo version. + if (StringUtils.isNotEmpty(tmpThemeProperty.getRequire()) && !VersionUtil.compareVersion(HaloConst.HALO_VERSION, tmpThemeProperty.getRequire())) { + throw new ThemeNotSupportException("当前主题仅支持 Halo " + tmpThemeProperty.getRequire() + " 以上的版本"); + } + // Copy the temporary path to current theme folder Path targetThemePath = themeWorkDir.resolve(tmpThemeProperty.getId()); FileUtils.copyFolder(themeTmpPath, targetThemePath); @@ -533,6 +540,9 @@ public class ThemeServiceImpl implements ThemeService { try { pullFromGit(updatingTheme); } catch (Exception e) { + if (e instanceof ThemeNotSupportException) { + throw (ThemeNotSupportException) e; + } throw new ThemeUpdateException("主题更新失败!您与主题作者可能同时更改了同一个文件,您也可以尝试删除主题并重新拉取最新的主题", e).setErrorData(themeId); } @@ -579,6 +589,11 @@ public class ThemeServiceImpl implements ThemeService { throw new ServiceException("上传的主题包不是该主题的更新包: " + file.getOriginalFilename()); } + // Not support current halo version. + if (StringUtils.isNotEmpty(prepareThemeProperty.getRequire()) && !VersionUtil.compareVersion(HaloConst.HALO_VERSION, prepareThemeProperty.getRequire())) { + throw new ThemeNotSupportException("新版本主题仅支持 Halo " + prepareThemeProperty.getRequire() + " 以上的版本"); + } + // Coping new theme files to old theme folder. FileUtils.copyFolder(preparePath, Paths.get(updatingTheme.getThemePath())); @@ -605,6 +620,15 @@ public class ThemeServiceImpl implements ThemeService { try { git = GitUtils.openOrInit(Paths.get(themeProperty.getThemePath())); + + Repository repository = git.getRepository(); + + RevWalk revWalk = new RevWalk(repository); + + Ref ref = repository.getAllRefs().get(Constants.HEAD); + + RevCommit lastCommit = revWalk.parseCommit(ref.getObjectId()); + // Force to set remote name git.remoteRemove().setRemoteName(THEME_PROVIDER_REMOTE_NAME).call(); RemoteConfig remoteConfig = git.remoteAdd() @@ -647,6 +671,19 @@ public class ThemeServiceImpl implements ThemeService { throw new ThemeUpdateException("拉取失败!您与主题作者可能同时更改了同一个文件"); } + + // updated successfully. + ThemeProperty updatedThemeProperty = getProperty(Paths.get(themeProperty.getThemePath())); + + // Not support current halo version. + if (StringUtils.isNotEmpty(updatedThemeProperty.getRequire()) && !VersionUtil.compareVersion(HaloConst.HALO_VERSION, updatedThemeProperty.getRequire())) { + // reset theme version + git.reset() + .setMode(ResetCommand.ResetType.HARD) + .setRef(lastCommit.getName()) + .call(); + throw new ThemeNotSupportException("新版本主题仅支持 Halo " + updatedThemeProperty.getRequire() + " 以上的版本"); + } } finally { GitUtils.closeQuietly(git); } @@ -826,7 +863,7 @@ public class ThemeServiceImpl implements ThemeService { // Set screenshots getScreenshotsFileName(themePath).ifPresent(screenshotsName -> themeProperty.setScreenshots(StringUtils.join(optionService.getBlogBaseUrl(), - "/", + "/themes/", FilenameUtils.getBasename(themeProperty.getThemePath()), "/", screenshotsName))); diff --git a/src/main/java/run/halo/app/utils/HaloUtils.java b/src/main/java/run/halo/app/utils/HaloUtils.java index 711f4e88b..086d7f972 100755 --- a/src/main/java/run/halo/app/utils/HaloUtils.java +++ b/src/main/java/run/halo/app/utils/HaloUtils.java @@ -17,7 +17,7 @@ import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR; * * @author ryanwang * @author johnniang - * @date 2017/12/22 + * @date 2017-12-22 */ @Slf4j public class HaloUtils { @@ -232,22 +232,6 @@ public class HaloUtils { return String.valueOf(System.currentTimeMillis()); } - /** - * Normalize url. - * - * @param url url must not be blank - * @return normalized url - */ - @NonNull - public static String normalizeUrl(@NonNull String url) { - Assert.hasText(url, "Url must not be blank"); - - StringUtils.removeEnd(url, "html"); - StringUtils.removeEnd(url, "htm"); - - return SlugUtils.slugify(url); - } - /** * Gets machine IP address. * diff --git a/src/main/java/run/halo/app/utils/VersionUtil.java b/src/main/java/run/halo/app/utils/VersionUtil.java new file mode 100644 index 000000000..e1d6f8406 --- /dev/null +++ b/src/main/java/run/halo/app/utils/VersionUtil.java @@ -0,0 +1,81 @@ +package run.halo.app.utils; + +import cn.hutool.core.util.StrUtil; + +import java.util.StringTokenizer; + +/** + * @author ryanwang + * @date 2020-02-03 + * @see com.sun.xml.internal.ws.util.VersionUtil + */ +public class VersionUtil { + + public VersionUtil() { + } + + public static int[] getCanonicalVersion(String version) { + int[] canonicalVersion = new int[]{1, 1, 0, 0}; + StringTokenizer tokenizer = new StringTokenizer(version, "."); + String token = tokenizer.nextToken(); + canonicalVersion[0] = Integer.parseInt(token); + token = tokenizer.nextToken(); + StringTokenizer subTokenizer; + if (!token.contains(StrUtil.UNDERLINE)) { + canonicalVersion[1] = Integer.parseInt(token); + } else { + subTokenizer = new StringTokenizer(token, "_"); + canonicalVersion[1] = Integer.parseInt(subTokenizer.nextToken()); + canonicalVersion[3] = Integer.parseInt(subTokenizer.nextToken()); + } + + if (tokenizer.hasMoreTokens()) { + token = tokenizer.nextToken(); + if (!token.contains(StrUtil.UNDERLINE)) { + canonicalVersion[2] = Integer.parseInt(token); + if (tokenizer.hasMoreTokens()) { + canonicalVersion[3] = Integer.parseInt(tokenizer.nextToken()); + } + } else { + subTokenizer = new StringTokenizer(token, "_"); + canonicalVersion[2] = Integer.parseInt(subTokenizer.nextToken()); + canonicalVersion[3] = Integer.parseInt(subTokenizer.nextToken()); + } + } + + return canonicalVersion; + } + + public static int compare(String version1, String version2) { + int[] canonicalVersion1 = getCanonicalVersion(version1); + int[] canonicalVersion2 = getCanonicalVersion(version2); + if (canonicalVersion1[0] < canonicalVersion2[0]) { + return -1; + } else if (canonicalVersion1[0] > canonicalVersion2[0]) { + return 1; + } else if (canonicalVersion1[1] < canonicalVersion2[1]) { + return -1; + } else if (canonicalVersion1[1] > canonicalVersion2[1]) { + return 1; + } else if (canonicalVersion1[2] < canonicalVersion2[2]) { + return -1; + } else if (canonicalVersion1[2] > canonicalVersion2[2]) { + return 1; + } else if (canonicalVersion1[3] < canonicalVersion2[3]) { + return -1; + } else { + return canonicalVersion1[3] > canonicalVersion2[3] ? 1 : 0; + } + } + + /** + * Compare version. + * + * @param current current version. + * @param require require version. + * @return true or false. + */ + public static boolean compareVersion(String current, String require) { + return compare(current, require) >= 0; + } +} diff --git a/src/test/java/run/halo/app/utils/HaloUtilsTest.java b/src/test/java/run/halo/app/utils/HaloUtilsTest.java index 84dd52de5..295078a45 100644 --- a/src/test/java/run/halo/app/utils/HaloUtilsTest.java +++ b/src/test/java/run/halo/app/utils/HaloUtilsTest.java @@ -2,7 +2,6 @@ package run.halo.app.utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomUtils; -import org.junit.Assert; import org.junit.Test; import java.util.stream.IntStream; @@ -15,7 +14,8 @@ import static org.junit.Assert.assertThat; * Halo utilities test. * * @author johnniang - * @date 3/29/19 + * @author ryanwang + * @date 2019-03-29 */ @Slf4j public class HaloUtilsTest { diff --git a/src/test/java/run/halo/app/utils/VersionUtilTest.java b/src/test/java/run/halo/app/utils/VersionUtilTest.java new file mode 100644 index 000000000..b0323d208 --- /dev/null +++ b/src/test/java/run/halo/app/utils/VersionUtilTest.java @@ -0,0 +1,26 @@ +package run.halo.app.utils; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author ryanwang + * @date 2020-02-03 + */ +public class VersionUtilTest { + + @Test + public void compareVersion() { + Assert.assertTrue(VersionUtil.compareVersion("1.2.0", "1.1.1")); + + Assert.assertTrue(VersionUtil.compareVersion("1.2.1", "1.2.0")); + + Assert.assertTrue(VersionUtil.compareVersion("1.2.0", "1.1.1.0")); + + Assert.assertTrue(VersionUtil.compareVersion("1.2.0", "0.4.4")); + + Assert.assertFalse(VersionUtil.compareVersion("1.1.1", "1.2.0")); + + Assert.assertFalse(VersionUtil.compareVersion("0.0.1", "1.2.0")); + } +} \ No newline at end of file