Complete theme fetching feature

pull/146/head
johnniang 2019-04-19 13:57:28 +08:00
parent ef9b6eb9f8
commit 5b59a8ab50
7 changed files with 172 additions and 10 deletions

View File

@ -22,9 +22,9 @@ configurations {
}
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
runtimeClasspath {
extendsFrom developmentOnly
}
}
dependencies {
@ -50,6 +50,7 @@ dependencies {
implementation 'org.apache.commons:commons-lang3:3.8.1'
implementation 'org.apache.httpcomponents:httpclient:4.5.7'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.2'
implementation 'org.eclipse.jgit:org.eclipse.jgit:5.3.0.201903130848-r'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java'

View File

@ -221,4 +221,13 @@ public interface ThemeService {
*/
@NonNull
ThemeProperty add(@NonNull Path themeTmpPath) throws IOException;
/**
* Fetches a new theme.
*
* @param uri theme remote uri must not be null
* @return theme property
*/
@NonNull
ThemeProperty fetch(@NonNull String uri);
}

View File

@ -9,11 +9,15 @@ import freemarker.template.Configuration;
import freemarker.template.TemplateModelException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties;
@ -30,8 +34,10 @@ import run.halo.app.service.ThemeService;
import run.halo.app.service.support.HaloMediaType;
import run.halo.app.utils.FileUtils;
import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.HaloUtils;
import run.halo.app.utils.JsonUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@ -65,6 +71,9 @@ public class ThemeServiceImpl implements ThemeService {
private final ThemeConfigResolver themeConfigResolver;
private final ThemePropertyResolver themePropertyResolver;
private final RestTemplate restTemplate;
/**
* Activated theme id.
*/
@ -75,13 +84,16 @@ public class ThemeServiceImpl implements ThemeService {
StringCacheStore cacheStore,
Configuration configuration,
ThemeConfigResolver themeConfigResolver,
ThemePropertyResolver themePropertyResolver) {
ThemePropertyResolver themePropertyResolver,
RestTemplate restTemplate) {
this.optionService = optionService;
this.cacheStore = cacheStore;
this.configuration = configuration;
this.themeConfigResolver = themeConfigResolver;
workDir = Paths.get(haloProperties.getWorkDir(), THEME_FOLDER);
this.themePropertyResolver = themePropertyResolver;
this.restTemplate = restTemplate;
workDir = Paths.get(haloProperties.getWorkDir(), THEME_FOLDER);
}
@Override
@ -318,7 +330,7 @@ public class ThemeServiceImpl implements ThemeService {
try {
// Create temp directory
tempPath = Files.createTempDirectory("halo");
tempPath = createTempPath();
String basename = FilenameUtils.getBasename(file.getOriginalFilename());
Path themeTempPath = tempPath.resolve(basename);
@ -331,9 +343,7 @@ public class ThemeServiceImpl implements ThemeService {
// Unzip to temp path
FileUtils.unzip(zis, themeTempPath);
// Go to the base folder
// Add the theme to system
// Go to the base folder and add the theme into system
return add(FileUtils.skipZipParentFolder(themeTempPath));
} catch (IOException e) {
throw new ServiceException("Failed to upload theme file: " + file.getOriginalFilename(), e);
@ -350,6 +360,9 @@ public class ThemeServiceImpl implements ThemeService {
Assert.notNull(themeTmpPath, "Theme temporary path must not be null");
Assert.isTrue(Files.isDirectory(themeTmpPath), "Theme temporary path must be a directory");
log.debug("Children path of [{}]:", themeTmpPath);
Files.list(themeTmpPath).forEach(path -> log.debug(path.toString()));
// Check property config
ThemeProperty tmpThemeProperty = getProperty(themeTmpPath);
@ -375,6 +388,96 @@ public class ThemeServiceImpl implements ThemeService {
return property;
}
@Override
public ThemeProperty fetch(String uri) {
Assert.hasText(uri, "Theme remote uri must not be blank");
Path tmpPath = null;
try {
// Create temp path
tmpPath = createTempPath();
// Create temp path
Path themeTmpPath = tmpPath.resolve(HaloUtils.randomUUIDWithoutDash());
if (StringUtils.endsWithIgnoreCase(uri, ".git")) {
cloneFromGit(uri, themeTmpPath);
} else {
downloadZipAndUnzip(uri, themeTmpPath);
// } else {
// throw new UnsupportedMediaTypeException("Unsupported download type: " + uri);
}
return add(themeTmpPath);
} catch (IOException | GitAPIException e) {
throw new ServiceException("Failed to fetch theme from remote " + uri, e);
} finally {
FileUtils.deleteFolderQuietly(tmpPath);
}
}
/**
* Clones theme from git.
*
* @param gitUrl git url must not be blank
* @param targetPath target path must not be null
* @throws GitAPIException
*/
private void cloneFromGit(@NonNull String gitUrl, @NonNull Path targetPath) throws GitAPIException {
Assert.hasText(gitUrl, "Git url must not be blank");
Assert.notNull(targetPath, "Target path must not be null");
log.debug("Cloning git repo [{}] to [{}]", gitUrl, targetPath);
// Clone it
Git.cloneRepository()
.setURI(gitUrl)
.setDirectory(targetPath.toFile())
.call();
log.debug("Cloned git repo [{}]", gitUrl);
}
/**
* Downloads zip file and unzip it into specified path.
*
* @param zipUrl zip url must not be null
* @param targetPath target path must not be null
* @throws IOException
*/
private void downloadZipAndUnzip(@NonNull String zipUrl, @NonNull Path targetPath) throws IOException {
Assert.hasText(zipUrl, "Zip url must not be blank");
log.debug("Downloading [{}]", zipUrl);
// Download it
ResponseEntity<byte[]> downloadResponse = restTemplate.getForEntity(zipUrl, byte[].class);
log.debug("Download response: [{}]", downloadResponse.getStatusCode());
if (downloadResponse.getStatusCode().isError() || downloadResponse.getBody() == null) {
throw new ServiceException("Failed to download " + zipUrl + ", status: " + downloadResponse.getStatusCode());
}
log.debug("Downloaded [{}]", zipUrl);
// New zip input stream
ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(downloadResponse.getBody()));
// Unzip it
FileUtils.unzip(zis, targetPath);
}
/**
* Creates temporary path.
*
* @return temporary path
* @throws IOException
*/
@NonNull
private Path createTempPath() throws IOException {
return Files.createTempDirectory("halo");
}
/**
* Clears theme cache.
*/
@ -490,6 +593,8 @@ public class ThemeServiceImpl implements ThemeService {
}
}
log.warn("Property file was not found in [{}]", themePath);
return Optional.empty();
}

View File

@ -1,5 +1,6 @@
package run.halo.app.service.support;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
@ -12,6 +13,7 @@ import java.util.Map;
* @author johnniang
* @date 19-4-18
*/
@Slf4j
public class HaloMediaType extends MediaType {
/**
@ -24,9 +26,14 @@ public class HaloMediaType extends MediaType {
*/
public static final String APPLICATION_ZIP_VALUE = "application/zip";
public static final MediaType APPLICATION_GIT;
public static final String APPLICATION_GIT_VALUE = "application/git";
static {
APPLICATION_ZIP = valueOf(APPLICATION_ZIP_VALUE);
APPLICATION_GIT = valueOf(APPLICATION_GIT_VALUE);
}
public HaloMediaType(String type) {

View File

@ -66,10 +66,14 @@ public class FileUtils {
public static void deleteFolder(Path deletingPath) throws IOException {
Assert.notNull(deletingPath, "Deleting path must not be null");
log.debug("Deleting [{}]", deletingPath);
Files.walk(deletingPath)
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
log.debug("Deleted [{}] successfully", deletingPath);
}
/**

View File

@ -4,9 +4,9 @@ import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.handler.theme.config.support.Group;
import run.halo.app.handler.theme.config.support.ThemeProperty;
import run.halo.app.model.support.BaseResponse;
import run.halo.app.model.support.ThemeFile;
import run.halo.app.handler.theme.config.support.ThemeProperty;
import run.halo.app.service.ThemeService;
import run.halo.app.service.ThemeSettingService;
@ -122,4 +122,10 @@ public class ThemeController {
public ThemeProperty uploadTheme(@RequestPart("file") MultipartFile file) {
return themeService.upload(file);
}
@PostMapping("fetching")
@ApiOperation("Fetches a new theme")
public ThemeProperty fetchTheme(@RequestParam("uri") String uri) {
return themeService.fetch(uri);
}
}

View File

@ -0,0 +1,30 @@
package run.halo.app.service.support;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.net.URI;
import java.net.URISyntaxException;
/**
* @author johnniang
* @date 19-4-19
*/
@Slf4j
public class HaloMediaTypeTest {
@Test
public void gitUrlCheckTest() throws URISyntaxException {
String git = "https://github.com/halo-dev/halo.git";
URI uri = new URI(git);
log.debug(uri.toString());
git = "ssh://git@github.com:halo-dev/halo.git";
uri = new URI(git);
log.debug(uri.toString());
}
}