From 0c8955dfcf33e4dbeaf390180438e3b259519a3e Mon Sep 17 00:00:00 2001 From: johnniang Date: Thu, 30 May 2019 23:06:59 +0800 Subject: [PATCH] Complete theme updating --- .../controller/admin/api/ThemeController.java | 8 ++ .../app/exception/ThemeUpdateException.java | 18 ++++ .../impl/YamlThemePropertyResolver.java | 9 +- .../theme/config/support/ThemeProperty.java | 10 ++- .../run/halo/app/service/ThemeService.java | 19 ++++ .../app/service/impl/ThemeServiceImpl.java | 90 +++++++++++++++++++ .../java/run/halo/app/utils/FileUtils.java | 6 +- src/main/resources/application-dev.yaml | 4 +- .../templates/themes/anatole/theme.yaml | 1 + src/test/java/run/halo/app/utils/GitTest.java | 38 ++++++-- 10 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 src/main/java/run/halo/app/exception/ThemeUpdateException.java diff --git a/src/main/java/run/halo/app/controller/admin/api/ThemeController.java b/src/main/java/run/halo/app/controller/admin/api/ThemeController.java index 4734e1386..5955ee053 100644 --- a/src/main/java/run/halo/app/controller/admin/api/ThemeController.java +++ b/src/main/java/run/halo/app/controller/admin/api/ThemeController.java @@ -117,6 +117,13 @@ public class ThemeController { themeSettingService.save(settings, themeId); } + @PutMapping("{themeId}") + public ThemeProperty updateTheme(@PathVariable("themeId") String themeId, + @RequestPart(name = "file", required = false) MultipartFile file) { + + return themeService.update(themeId); + } + @DeleteMapping("{themeId}") @ApiOperation("Deletes a theme") public void deleteBy(@PathVariable("themeId") String themeId) { @@ -145,4 +152,5 @@ public class ThemeController { public BaseResponse exists(@RequestParam(value = "template") String template) { return BaseResponse.ok(themeService.templateExists(template)); } + } diff --git a/src/main/java/run/halo/app/exception/ThemeUpdateException.java b/src/main/java/run/halo/app/exception/ThemeUpdateException.java new file mode 100644 index 000000000..78b2bb276 --- /dev/null +++ b/src/main/java/run/halo/app/exception/ThemeUpdateException.java @@ -0,0 +1,18 @@ +package run.halo.app.exception; + +/** + * Theme update exception. + * + * @author johnniang + * @date 19-5-30 + */ +public class ThemeUpdateException extends ServiceException { + + public ThemeUpdateException(String message) { + super(message); + } + + public ThemeUpdateException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/run/halo/app/handler/theme/config/impl/YamlThemePropertyResolver.java b/src/main/java/run/halo/app/handler/theme/config/impl/YamlThemePropertyResolver.java index 8b3ef5f0b..a76be9e41 100644 --- a/src/main/java/run/halo/app/handler/theme/config/impl/YamlThemePropertyResolver.java +++ b/src/main/java/run/halo/app/handler/theme/config/impl/YamlThemePropertyResolver.java @@ -1,6 +1,8 @@ package run.halo.app.handler.theme.config.impl; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.springframework.stereotype.Service; import org.springframework.util.Assert; @@ -18,7 +20,12 @@ import java.io.IOException; @Service public class YamlThemePropertyResolver implements ThemePropertyResolver { - private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); + private final ObjectMapper yamlMapper; + + public YamlThemePropertyResolver() { + yamlMapper = new ObjectMapper(new YAMLFactory()); + yamlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } @Override public ThemeProperty resolve(String content) throws IOException { 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 6b79f41de..dcae2ae0c 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 @@ -28,6 +28,14 @@ public class ThemeProperty { */ private String website; + /** + * Theme remote branch.(default is master) + */ + private String branch; + + /** + * Theme repo url. + */ private String repo; /** @@ -51,7 +59,7 @@ public class ThemeProperty { private Author author; /** - * Theme path. + * Theme full path. */ private String themePath; diff --git a/src/main/java/run/halo/app/service/ThemeService.java b/src/main/java/run/halo/app/service/ThemeService.java index 0204573f9..1835e835d 100644 --- a/src/main/java/run/halo/app/service/ThemeService.java +++ b/src/main/java/run/halo/app/service/ThemeService.java @@ -71,6 +71,16 @@ public interface ThemeService { */ String CUSTOM_SHEET_PREFIX = "sheet_"; + /** + * Theme provider remote name. + */ + String THEME_PROVIDER_REMOTE_NAME = "theme-provider"; + + /** + * Default remote branch name. + */ + String DEFAULT_REMOTE_BRANCH = "master"; + /** * Get theme property by theme id. * @@ -241,4 +251,13 @@ public interface ThemeService { * Reloads themes */ void reload(); + + /** + * Updates theme by theme id. + * + * @param themeId theme id must not be blank + * @return theme property + */ + @NonNull + ThemeProperty update(@NonNull String themeId); } 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 11bf16cb6..d9f161ab0 100644 --- a/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java @@ -3,7 +3,14 @@ package run.halo.app.service.impl; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeCommand; +import org.eclipse.jgit.api.PullResult; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.ResponseEntity; import org.springframework.lang.NonNull; @@ -32,7 +39,9 @@ import run.halo.app.utils.FilenameUtils; import run.halo.app.utils.HaloUtils; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -460,6 +469,87 @@ public class ThemeServiceImpl implements ThemeService { eventPublisher.publishEvent(new ThemeUpdatedEvent(this)); } + @Override + public ThemeProperty update(String themeId) { + Assert.hasText(themeId, "Theme id must not be blank"); + + ThemeProperty updatingTheme = getThemeOfNonNullBy(themeId); + + try { + pullFromGit(updatingTheme); + } catch (Exception e) { + throw new ThemeUpdateException("主题更新失败!您与主题作者可能同时更改了同一个文件,您也可以尝试删除主题并重新拉取最新的主题", e).setErrorData(themeId); + } + + eventPublisher.publishEvent(new ThemeUpdatedEvent(this)); + + return getThemeOfNonNullBy(themeId); + } + + private void pullFromGit(@NonNull ThemeProperty themeProperty) throws IOException, GitAPIException, URISyntaxException { + Assert.notNull(themeProperty, "Theme property must not be null"); + + // Get branch + String branch = StringUtils.isBlank(themeProperty.getBranch()) ? + DEFAULT_REMOTE_BRANCH : themeProperty.getBranch(); + + File themeFolder = Paths.get(themeProperty.getThemePath()).toFile(); + // Open the theme path + Git git; + + try { + git = Git.open(themeFolder); + } catch (RepositoryNotFoundException e) { + // Repository is not initialized + git = Git.init().setDirectory(themeFolder).call(); + } + + // Force to set remote name + git.remoteRemove().setRemoteName(THEME_PROVIDER_REMOTE_NAME).call(); + RemoteConfig remoteConfig = git.remoteAdd() + .setName(THEME_PROVIDER_REMOTE_NAME) + .setUri(new URIish(themeProperty.getRepo())) + .call(); + + // Add all changes + git.add() + .addFilepattern(".") + .call(); + // Commit the changes + git.commit().setMessage("Commit by halo automatically").call(); + + // Check out to specified branch + if (!StringUtils.equalsIgnoreCase(branch, git.getRepository().getBranch())) { + boolean present = git.branchList() + .call() + .stream() + .map(Ref::getName) + .anyMatch(name -> StringUtils.equalsIgnoreCase(name, branch)); + + git.checkout() + .setCreateBranch(true) + .setForced(!present) + .setName(branch) + .call(); + } + + // Pull with rebasing + PullResult pullResult = git.pull() + .setRemote(remoteConfig.getName()) + .setRemoteBranchName(branch) + .setRebase(true) + .call(); + + if (!pullResult.isSuccessful()) { + log.debug("Rebase result: [{}]", pullResult.getRebaseResult()); + log.debug("Merge result: [{}]", pullResult.getMergeResult()); + + throw new ThemeUpdateException("拉取失败!您与主题作者可能同时更改了同一个文件"); + } + // Close git + git.close(); + } + /** * Clones theme from git. * diff --git a/src/main/java/run/halo/app/utils/FileUtils.java b/src/main/java/run/halo/app/utils/FileUtils.java index d7aac7685..033a9343e 100644 --- a/src/main/java/run/halo/app/utils/FileUtils.java +++ b/src/main/java/run/halo/app/utils/FileUtils.java @@ -64,7 +64,7 @@ public class FileUtils { * * @param deletingPath deleting path must not be null */ - public static void deleteFolder(Path deletingPath) throws IOException { + public static void deleteFolder(@NonNull Path deletingPath) throws IOException { Assert.notNull(deletingPath, "Deleting path must not be null"); log.debug("Deleting [{}]", deletingPath); @@ -249,9 +249,9 @@ public class FileUtils { /** * Deletes folder quietly. * - * @param deletingPath deleting path must not be null + * @param deletingPath deleting path */ - public static void deleteFolderQuietly(@NonNull Path deletingPath) { + public static void deleteFolderQuietly(@Nullable Path deletingPath) { try { if (deletingPath != null) { FileUtils.deleteFolder(deletingPath); diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index b81ba59ba..39c53a893 100755 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -44,8 +44,8 @@ spring: open-in-view: false servlet: multipart: - max-file-size: 10MB - max-request-size: 10MB + max-file-size: 10240MB + max-request-size: 10240MB cache: type: none diff --git a/src/main/resources/templates/themes/anatole/theme.yaml b/src/main/resources/templates/themes/anatole/theme.yaml index 8b352bf15..53cf5f148 100644 --- a/src/main/resources/templates/themes/anatole/theme.yaml +++ b/src/main/resources/templates/themes/anatole/theme.yaml @@ -5,5 +5,6 @@ author: website: https://www.caicai.me description: A other Halo theme logo: https://avatars1.githubusercontent.com/u/1811819?s=460&v=4 +website: https://github.com/halo-dev/halo-theme-anatole repo: https://github.com/halo-dev/halo-theme-anatole version: 1.0 \ No newline at end of file diff --git a/src/test/java/run/halo/app/utils/GitTest.java b/src/test/java/run/halo/app/utils/GitTest.java index 75558c3c5..93bfbc752 100644 --- a/src/test/java/run/halo/app/utils/GitTest.java +++ b/src/test/java/run/halo/app/utils/GitTest.java @@ -1,15 +1,23 @@ package run.halo.app.utils; +import lombok.extern.slf4j.Slf4j; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Status; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; +import java.util.Set; /** * Git test. @@ -17,17 +25,23 @@ import java.nio.file.Path; * @author johnniang * @date 19-5-21 */ -@Ignore +@Slf4j public class GitTest { - private final Path tempPath; + private static Path tempPath; - public GitTest() throws IOException { + @BeforeClass + public static void setUp() throws IOException { tempPath = Files.createTempDirectory("git-test"); } + @AfterClass + public static void destroy() throws IOException { + FileUtils.deleteFolder(tempPath); + } + @Test(expected = RepositoryNotFoundException.class) - public void openTest() throws IOException { + public void openFailureTest() throws IOException { Git.open(tempPath.toFile()); } @@ -37,17 +51,29 @@ public class GitTest { } @Test - public void statusTest() throws GitAPIException { + public void statusSuccessfulTest() throws GitAPIException { Git git = Git.init().setDirectory(tempPath.toFile()).call(); - git.status().call(); + Status status = git.status().call(); + log.debug("Status missing: [{}]", status.getMissing()); } @Test + public void remoteAddTest() throws GitAPIException, URISyntaxException { + Git git = Git.init().setDirectory(tempPath.toFile()).call(); + git.remoteRemove().setRemoteName("theme-provider").call(); + git.remoteAdd().setName("theme-provider").setUri(new URIish("https://github.com/halo-dev/halo-theme-pinghsu.git")).call(); + List remoteConfigs = git.remoteList().call(); + remoteConfigs.forEach(remoteConfig -> log.debug("name: [{}], url: [{}]", remoteConfig.getName(), remoteConfig.getURIs())); + } + + @Test + @Ignore public void cloneTest() throws GitAPIException { cloneRepository(); } @Test + @Ignore public void pullTest() throws GitAPIException { Git git = cloneRepository(); git.pull().call();