Complete theme updating

pull/172/head
johnniang 2019-05-30 23:06:59 +08:00
parent bf6b11eb2e
commit 0c8955dfcf
10 changed files with 190 additions and 13 deletions

View File

@ -117,6 +117,13 @@ public class ThemeController {
themeSettingService.save(settings, themeId); 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}") @DeleteMapping("{themeId}")
@ApiOperation("Deletes a theme") @ApiOperation("Deletes a theme")
public void deleteBy(@PathVariable("themeId") String themeId) { public void deleteBy(@PathVariable("themeId") String themeId) {
@ -145,4 +152,5 @@ public class ThemeController {
public BaseResponse exists(@RequestParam(value = "template") String template) { public BaseResponse exists(@RequestParam(value = "template") String template) {
return BaseResponse.ok(themeService.templateExists(template)); return BaseResponse.ok(themeService.templateExists(template));
} }
} }

View File

@ -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);
}
}

View File

@ -1,6 +1,8 @@
package run.halo.app.handler.theme.config.impl; 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.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -18,7 +20,12 @@ import java.io.IOException;
@Service @Service
public class YamlThemePropertyResolver implements ThemePropertyResolver { 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 @Override
public ThemeProperty resolve(String content) throws IOException { public ThemeProperty resolve(String content) throws IOException {

View File

@ -28,6 +28,14 @@ public class ThemeProperty {
*/ */
private String website; private String website;
/**
* Theme remote branch.(default is master)
*/
private String branch;
/**
* Theme repo url.
*/
private String repo; private String repo;
/** /**
@ -51,7 +59,7 @@ public class ThemeProperty {
private Author author; private Author author;
/** /**
* Theme path. * Theme full path.
*/ */
private String themePath; private String themePath;

View File

@ -71,6 +71,16 @@ public interface ThemeService {
*/ */
String CUSTOM_SHEET_PREFIX = "sheet_"; 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. * Get theme property by theme id.
* *
@ -241,4 +251,13 @@ public interface ThemeService {
* Reloads themes * Reloads themes
*/ */
void reload(); void reload();
/**
* Updates theme by theme id.
*
* @param themeId theme id must not be blank
* @return theme property
*/
@NonNull
ThemeProperty update(@NonNull String themeId);
} }

View File

@ -3,7 +3,14 @@ package run.halo.app.service.impl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.Git; 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.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.context.ApplicationEventPublisher;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
@ -32,7 +39,9 @@ import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.HaloUtils; import run.halo.app.utils.HaloUtils;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -460,6 +469,87 @@ public class ThemeServiceImpl implements ThemeService {
eventPublisher.publishEvent(new ThemeUpdatedEvent(this)); 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. * Clones theme from git.
* *

View File

@ -64,7 +64,7 @@ public class FileUtils {
* *
* @param deletingPath deleting path must not be null * @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"); Assert.notNull(deletingPath, "Deleting path must not be null");
log.debug("Deleting [{}]", deletingPath); log.debug("Deleting [{}]", deletingPath);
@ -249,9 +249,9 @@ public class FileUtils {
/** /**
* Deletes folder quietly. * 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 { try {
if (deletingPath != null) { if (deletingPath != null) {
FileUtils.deleteFolder(deletingPath); FileUtils.deleteFolder(deletingPath);

View File

@ -44,8 +44,8 @@ spring:
open-in-view: false open-in-view: false
servlet: servlet:
multipart: multipart:
max-file-size: 10MB max-file-size: 10240MB
max-request-size: 10MB max-request-size: 10240MB
cache: cache:
type: none type: none

View File

@ -5,5 +5,6 @@ author:
website: https://www.caicai.me website: https://www.caicai.me
description: A other Halo theme description: A other Halo theme
logo: https://avatars1.githubusercontent.com/u/1811819?s=460&v=4 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 repo: https://github.com/halo-dev/halo-theme-anatole
version: 1.0 version: 1.0

View File

@ -1,15 +1,23 @@
package run.halo.app.utils; package run.halo.app.utils;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status; import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.RepositoryNotFoundException; 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.Ignore;
import org.junit.Test; import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.Set;
/** /**
* Git test. * Git test.
@ -17,17 +25,23 @@ import java.nio.file.Path;
* @author johnniang * @author johnniang
* @date 19-5-21 * @date 19-5-21
*/ */
@Ignore @Slf4j
public class GitTest { 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"); tempPath = Files.createTempDirectory("git-test");
} }
@AfterClass
public static void destroy() throws IOException {
FileUtils.deleteFolder(tempPath);
}
@Test(expected = RepositoryNotFoundException.class) @Test(expected = RepositoryNotFoundException.class)
public void openTest() throws IOException { public void openFailureTest() throws IOException {
Git.open(tempPath.toFile()); Git.open(tempPath.toFile());
} }
@ -37,17 +51,29 @@ public class GitTest {
} }
@Test @Test
public void statusTest() throws GitAPIException { public void statusSuccessfulTest() throws GitAPIException {
Git git = Git.init().setDirectory(tempPath.toFile()).call(); Git git = Git.init().setDirectory(tempPath.toFile()).call();
git.status().call(); Status status = git.status().call();
log.debug("Status missing: [{}]", status.getMissing());
} }
@Test @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<RemoteConfig> remoteConfigs = git.remoteList().call();
remoteConfigs.forEach(remoteConfig -> log.debug("name: [{}], url: [{}]", remoteConfig.getName(), remoteConfig.getURIs()));
}
@Test
@Ignore
public void cloneTest() throws GitAPIException { public void cloneTest() throws GitAPIException {
cloneRepository(); cloneRepository();
} }
@Test @Test
@Ignore
public void pullTest() throws GitAPIException { public void pullTest() throws GitAPIException {
Git git = cloneRepository(); Git git = cloneRepository();
git.pull().call(); git.pull().call();