Refactor theme configuration and theme property resolvers

pull/146/head
johnniang 2019-04-11 23:42:24 +08:00
parent eeeee26b7c
commit dda7225b2d
19 changed files with 223 additions and 103 deletions

View File

@ -0,0 +1,18 @@
package run.halo.app.exception;
/**
* Theme configuration missing exception.
*
* @author johnniang
* @date 4/11/19
*/
public class ThemeConfigMissingException extends BadRequestException {
public ThemeConfigMissingException(String message) {
super(message);
}
public ThemeConfigMissingException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,18 @@
package run.halo.app.exception;
/**
* Theme property missing exception.
*
* @author johnniang
* @date 4/11/19
*/
public class ThemePropertyMissingException extends BadRequestException {
public ThemePropertyMissingException(String message) {
super(message);
}
public ThemePropertyMissingException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,6 +1,7 @@
package run.halo.app.handler.theme;
import org.springframework.lang.NonNull;
import run.halo.app.handler.theme.support.Group;
import java.io.IOException;
import java.util.List;
@ -22,4 +23,5 @@ public interface ThemeConfigResolver {
*/
@NonNull
List<Group> resolve(@NonNull String content) throws IOException;
}

View File

@ -4,6 +4,7 @@ import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import run.halo.app.handler.theme.impl.YamlThemeConfigResolverImpl;
import run.halo.app.handler.theme.support.Group;
import java.io.IOException;
import java.util.List;

View File

@ -0,0 +1,24 @@
package run.halo.app.handler.theme;
import org.springframework.lang.NonNull;
import run.halo.app.handler.theme.support.ThemeProperty;
import java.io.IOException;
/**
* Theme file resolver.
*
* @author johnniang
* @date 4/11/19
*/
public interface ThemePropertyResolver {
/**
* Resolves the theme file.
*
* @param content file content must not be null
* @return theme file
*/
@NonNull
ThemeProperty resolve(@NonNull String content) throws IOException;
}

View File

@ -3,10 +3,11 @@ package run.halo.app.handler.theme.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.springframework.lang.Nullable;
import run.halo.app.handler.theme.Group;
import run.halo.app.handler.theme.Item;
import run.halo.app.handler.theme.Option;
import org.springframework.stereotype.Service;
import run.halo.app.handler.theme.ThemeConfigResolver;
import run.halo.app.handler.theme.support.Group;
import run.halo.app.handler.theme.support.Item;
import run.halo.app.handler.theme.support.Option;
import run.halo.app.model.enums.DataType;
import run.halo.app.model.enums.InputType;
@ -22,6 +23,7 @@ import java.util.Map;
* @author johnniang
* @date 4/10/19
*/
@Service
public class YamlThemeConfigResolverImpl implements ThemeConfigResolver {
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());

View File

@ -0,0 +1,29 @@
package run.halo.app.handler.theme.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import run.halo.app.handler.theme.ThemePropertyResolver;
import run.halo.app.handler.theme.support.ThemeProperty;
import java.io.IOException;
/**
* Yaml theme file resolver.
*
* @author johnniang
* @date 4/11/19
*/
@Service
public class YamlThemePropertyResolver implements ThemePropertyResolver {
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
@Override
public ThemeProperty resolve(String content) throws IOException {
Assert.hasText(content, "Theme file content must not be null");
return yamlMapper.readValue(content, ThemeProperty.class);
}
}

View File

@ -1,4 +1,4 @@
package run.halo.app.handler.theme;
package run.halo.app.handler.theme.support;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package run.halo.app.handler.theme;
package run.halo.app.handler.theme.support;
import lombok.Data;
import run.halo.app.model.enums.DataType;

View File

@ -1,4 +1,4 @@
package run.halo.app.handler.theme;
package run.halo.app.handler.theme.support;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package run.halo.app.model.support;
package run.halo.app.handler.theme.support;
import lombok.Data;
@ -44,12 +44,7 @@ public class ThemeProperty {
/**
* Theme author.
*/
private String author;
/**
* Theme author website.
*/
private String authorWebsite;
private Author author;
/**
* Folder name.
@ -70,4 +65,18 @@ public class ThemeProperty {
* Screenshots url.
*/
private String screenshots;
@Data
public static class Author {
/**
* Author name.
*/
private String name;
/**
* Author website.
*/
private String website;
}
}

View File

@ -44,11 +44,6 @@ public class HaloConst {
*/
public static Map<String, String> OWO_MAP = Collections.emptyMap();
/**
* All of the themes
*/
public static List<Theme> THEMES;
/**
* user_session
*/

View File

@ -1,33 +0,0 @@
package run.halo.app.model.support;
import lombok.Data;
import java.io.Serializable;
/**
* Theme DTO
*
* @author : RYAN0UP
* @date : 2018/1/3
*/
@Data
@Deprecated
public class Theme implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Theme key,is theme folder name.
*/
private String key;
/**
* Is support setting options
*/
private boolean hasOptions;
/**
* Theme properties
*/
private ThemeProperty properties;
}

View File

@ -1,10 +1,9 @@
package run.halo.app.service;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import run.halo.app.handler.theme.Group;
import run.halo.app.handler.theme.support.Group;
import run.halo.app.handler.theme.support.ThemeProperty;
import run.halo.app.model.support.ThemeFile;
import run.halo.app.model.support.ThemeProperty;
import java.io.File;
import java.nio.file.Path;
@ -20,7 +19,12 @@ public interface ThemeService {
/**
* Theme property file name.
*/
String THEME_PROPERTY_FILE_NAME = "theme.properties";
String THEME_PROPERTY_FILE_NAME = "theme.yaml";
/**
* Theme property file name.
*/
String[] THEME_PROPERTY_FILE_NAMES = {"theme.yaml", "theme.yml"};
/**
@ -36,7 +40,7 @@ public interface ThemeService {
/**
* These file names cannot be displayed.
*/
String[] FILTER_FILES = {".git", ".DS_Store", THEME_PROPERTY_FILE_NAME, "options.yaml", "option.yml"};
String[] FILTER_FILES = {".git", ".DS_Store", "theme.yaml", "theme.yml", "options.yaml", "option.yml"};
/**
* Theme folder location.
@ -82,6 +86,7 @@ public interface ThemeService {
*
* @return list of themes
*/
@NonNull
List<ThemeProperty> getThemes();
/**

View File

@ -15,16 +15,14 @@ import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.BadRequestException;
import run.halo.app.exception.ForbiddenException;
import run.halo.app.exception.NotFoundException;
import run.halo.app.exception.ServiceException;
import run.halo.app.handler.theme.Group;
import run.halo.app.handler.theme.ThemeConfigResolvers;
import run.halo.app.exception.*;
import run.halo.app.handler.theme.ThemeConfigResolver;
import run.halo.app.handler.theme.ThemePropertyResolver;
import run.halo.app.handler.theme.support.Group;
import run.halo.app.handler.theme.support.ThemeProperty;
import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.model.support.HaloConst;
import run.halo.app.model.support.ThemeFile;
import run.halo.app.model.support.ThemeProperty;
import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService;
import run.halo.app.utils.FilenameUtils;
@ -32,8 +30,6 @@ import run.halo.app.utils.JsonUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -54,10 +50,16 @@ public class ThemeServiceImpl implements ThemeService {
* Theme work directory.
*/
private final Path workDir;
private final OptionService optionService;
private final StringCacheStore cacheStore;
private final Configuration configuration;
private final ThemeConfigResolvers resolvers;
private final ThemeConfigResolver themeConfigResolver;
private final ThemePropertyResolver themePropertyResolver;
/**
* Activated theme id.
*/
@ -67,12 +69,14 @@ public class ThemeServiceImpl implements ThemeService {
OptionService optionService,
StringCacheStore cacheStore,
Configuration configuration,
ThemeConfigResolvers resolvers) {
ThemeConfigResolver themeConfigResolver,
ThemePropertyResolver themePropertyResolver) {
this.optionService = optionService;
this.cacheStore = cacheStore;
this.configuration = configuration;
this.resolvers = resolvers;
this.themeConfigResolver = themeConfigResolver;
workDir = Paths.get(haloProperties.getWorkDir(), THEME_FOLDER);
this.themePropertyResolver = themePropertyResolver;
}
@Override
@ -89,13 +93,13 @@ public class ThemeServiceImpl implements ThemeService {
log.debug("Themes type: [{}]", themes.getClass());
log.debug("Themes: [{}]", themes);
return themes.stream().filter(themeProperty -> themeProperty.getId().equals(themeId)).findFirst();
return themes.stream().filter(themeProperty -> StringUtils.equals(themeProperty.getId(), themeId)).findFirst();
}
@Override
public List<ThemeProperty> getThemes() {
// Fetch themes from cache
return cacheStore.get(THEMES_CACHE_KEY).map(themesCache -> {
List<ThemeProperty> result = cacheStore.get(THEMES_CACHE_KEY).map(themesCache -> {
try {
// Convert to theme properties
ThemeProperty[] themeProperties = JsonUtils.jsonToObject(themesCache, ThemeProperty[].class);
@ -123,6 +127,8 @@ public class ThemeServiceImpl implements ThemeService {
throw new ServiceException("Themes scan failed", e);
}
});
return CollectionUtils.isEmpty(result) ? Collections.emptyList() : result;
}
@Override
@ -141,10 +147,9 @@ public class ThemeServiceImpl implements ThemeService {
@Override
public List<String> getCustomTpl(String themeId) {
// TODO 这里的参数是有问题的,等待修复。
final List<String> templates = new ArrayList<>();
final File themePath = new File(getThemeBasePath(), themeId);
final File[] themeFiles = themePath.listFiles();
List<String> templates = new ArrayList<>();
File themePath = new File(getThemeBasePath(), themeId);
File[] themeFiles = themePath.listFiles();
if (null != themeFiles && themeFiles.length > 0) {
for (File file : themeFiles) {
String[] split = StrUtil.removeSuffix(file.getName(), HaloConst.SUFFIX_FTL).split("_");
@ -245,8 +250,9 @@ public class ThemeServiceImpl implements ThemeService {
// Read the yaml file
String optionContent = new String(Files.readAllBytes(optionsPath));
// Resolve it
return resolvers.resolve(optionContent);
return themeConfigResolver.resolve(optionContent);
}
return Collections.emptyList();
@ -394,35 +400,47 @@ public class ThemeServiceImpl implements ThemeService {
}
}
@Nullable
private Path getPropertyPath(@NonNull Path themePath) {
Assert.notNull(themePath, "Theme path must not be null");
for (String propertyPathName : THEME_PROPERTY_FILE_NAMES) {
Path propertyPath = themePath.resolve(propertyPathName);
log.debug("Attempting to find property file: [{}]", propertyPath);
if (Files.exists(propertyPath) && Files.isReadable(propertyPath)) {
log.debug("Found property file: [{}]", propertyPath);
return propertyPath;
}
}
return null;
}
/**
* Gets theme property.
*
* @param themePath must not be null
* @return theme property
*/
@NonNull
private ThemeProperty getProperty(@NonNull Path themePath) {
Assert.notNull(themePath, "Theme path must not be null");
Path propertyPath = themePath.resolve(THEME_PROPERTY_FILE_NAME);
if (!Files.exists(propertyPath)) {
return null;
Path propertyPath = getPropertyPath(themePath);
if (propertyPath == null) {
throw new ThemePropertyMissingException(themePath + " dose not exist any theme property file").setErrorData(themePath);
}
try {
Properties properties = new Properties();
// Load properties
properties.load(new InputStreamReader(Files.newInputStream(propertyPath), StandardCharsets.UTF_8));
// Get property content
String propertyContent = new String(Files.readAllBytes(propertyPath));
ThemeProperty themeProperty = new ThemeProperty();
themeProperty.setId(properties.getProperty("theme.id"));
themeProperty.setName(properties.getProperty("theme.name"));
themeProperty.setWebsite(properties.getProperty("theme.website"));
themeProperty.setDescription(properties.getProperty("theme.description"));
themeProperty.setLogo(properties.getProperty("theme.logo"));
themeProperty.setVersion(properties.getProperty("theme.version"));
themeProperty.setAuthor(properties.getProperty("theme.author"));
themeProperty.setAuthorWebsite(properties.getProperty("theme.author.website"));
// Resolve the base properties
ThemeProperty themeProperty = themePropertyResolver.resolve(propertyContent);
// Resolve additional properties
themeProperty.setThemePath(themePath.toString());
themeProperty.setHasOptions(hasOptions(themePath));
themeProperty.setActivated(false);
@ -435,15 +453,14 @@ public class ThemeServiceImpl implements ThemeService {
"/",
screenshotsName)));
if (themeProperty.getId().equals(getActivatedThemeId())) {
if (StringUtils.equals(themeProperty.getId(), getActivatedThemeId())) {
// Set activation
themeProperty.setActivated(true);
}
return themeProperty;
} catch (IOException e) {
// TODO Consider to ignore this error, then return null
throw new ServiceException("Failed to load: " + themePath.toString(), e);
throw new ThemePropertyMissingException("Cannot resolve theme property", e).setErrorData(propertyPath.toString());
}
}

View File

@ -2,10 +2,10 @@ package run.halo.app.web.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import run.halo.app.handler.theme.Group;
import run.halo.app.handler.theme.support.Group;
import run.halo.app.model.support.BaseResponse;
import run.halo.app.model.support.ThemeFile;
import run.halo.app.model.support.ThemeProperty;
import run.halo.app.handler.theme.support.ThemeProperty;
import run.halo.app.service.ThemeService;
import run.halo.app.service.ThemeSettingService;

View File

@ -23,10 +23,10 @@ spring:
password: 123456
# MySql 配置
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
# username: root
# password: 123456
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
# username: root
# password: 123456
h2:
console:
@ -49,8 +49,8 @@ logging:
level:
run.halo.app: DEBUG
org.hibernate: INFO
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.hibernate.type.descriptor.sql.BasicExtractor: TRACE
org.hibernate.type.descriptor.sql.BasicBinder: INFO
org.hibernate.type.descriptor.sql.BasicExtractor: INFO
file: ./logs/log.log
halo:

View File

@ -0,0 +1,34 @@
package run.halo.app.handler.theme.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.junit.Test;
import run.halo.app.handler.theme.support.ThemeProperty;
import java.io.IOException;
/**
* @author johnniang
* @date 4/11/19
*/
public class YamlThemePropertyResolverTest {
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
@Test
public void directResolveTest() throws IOException {
String yaml = "id: viosey_material\n" +
"name: Material\n" +
"author:\n" +
" name: Viosey\n" +
" website: https://viosey.com\n" +
"description: Nature, Pure | 原质,纯粹\n" +
"logo: https://avatars0.githubusercontent.com/u/8141232?s=460&v=4\n" +
"website: https://github.com/viosey/hexo-theme-material\n" +
"version: 1.0";
ThemeProperty themeProperty = yamlMapper.readValue(yaml, ThemeProperty.class);
System.out.println(themeProperty);
}
}

View File

@ -1,7 +1,7 @@
package run.halo.app.utils;
import org.junit.Test;
import run.halo.app.handler.theme.Group;
import run.halo.app.handler.theme.support.Group;
import run.halo.app.handler.theme.impl.YamlThemeConfigResolverImpl;
import java.io.IOException;
@ -70,7 +70,6 @@ public class YamlTest {
}
@Test
@SuppressWarnings("unchecked")
public void readAnotherYamlTest() throws IOException {
String yaml = "sns:\n" +
" label: 社交资料设置\n" +