mirror of https://github.com/halo-dev/halo
Complete theme fetching feature
parent
ef9b6eb9f8
commit
5b59a8ab50
|
@ -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'
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue