From 468a0323b48978210c9e4430f26b6eef308c7820 Mon Sep 17 00:00:00 2001 From: johnniang Date: Tue, 9 Apr 2019 01:18:52 +0800 Subject: [PATCH] Refactor and beauty codes --- .../halo/app/listener/StartedListener.java | 14 +- .../halo/app/model/entity/ThemeSetting.java | 15 +- .../halo/app/model/enums/OptionSource.java | 25 -- .../converter/OptionSourceConverter.java | 19 -- .../run/halo/app/model/support/Theme.java | 3 +- .../app/model/support/ThemeProperties.java | 27 -- .../halo/app/model/support/ThemeProperty.java | 61 +++++ .../run/halo/app/service/ThemeService.java | 25 +- .../app/service/impl/ThemeServiceImpl.java | 231 ++++++++++++------ .../service/impl/ThemeSettingServiceImpl.java | 1 - .../admin/api/OptionController.java | 4 - .../controller/admin/api/ThemeController.java | 22 +- .../web/controller/core/CommonController.java | 4 +- .../controller/core/InstallController.java | 1 - 14 files changed, 256 insertions(+), 196 deletions(-) delete mode 100644 src/main/java/run/halo/app/model/enums/OptionSource.java delete mode 100644 src/main/java/run/halo/app/model/enums/converter/OptionSourceConverter.java delete mode 100644 src/main/java/run/halo/app/model/support/ThemeProperties.java create mode 100644 src/main/java/run/halo/app/model/support/ThemeProperty.java diff --git a/src/main/java/run/halo/app/listener/StartedListener.java b/src/main/java/run/halo/app/listener/StartedListener.java index f8c6e018e..d9d646642 100644 --- a/src/main/java/run/halo/app/listener/StartedListener.java +++ b/src/main/java/run/halo/app/listener/StartedListener.java @@ -17,7 +17,6 @@ import run.halo.app.model.params.UserParam; import run.halo.app.model.properties.BlogProperties; import run.halo.app.model.properties.PrimaryProperties; import run.halo.app.model.support.HaloConst; -import run.halo.app.model.support.Theme; import run.halo.app.service.OptionService; import run.halo.app.service.ThemeService; import run.halo.app.service.UserService; @@ -63,7 +62,6 @@ public class StartedListener implements ApplicationListener themes = themeService.getThemes(); - if (null != themes) { - HaloConst.THEMES = themes; - } - } - /** * Get active theme */ private void cacheActiveTheme() { try { - configuration.setSharedVariable("themeName", themeService.getTheme()); + configuration.setSharedVariable("themeName", themeService.getActivatedTheme()); } catch (TemplateModelException e) { log.error("", e); } diff --git a/src/main/java/run/halo/app/model/entity/ThemeSetting.java b/src/main/java/run/halo/app/model/entity/ThemeSetting.java index 966f6535d..cd2bcffd1 100644 --- a/src/main/java/run/halo/app/model/entity/ThemeSetting.java +++ b/src/main/java/run/halo/app/model/entity/ThemeSetting.java @@ -23,16 +23,23 @@ import javax.persistence.*; @EqualsAndHashCode(callSuper = true) public class ThemeSetting extends BaseEntity { + /** + * Theme id as id. + */ @Id + @Column(name = "id", columnDefinition = "varchar(255) not null") @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - @Column(name = "theme", columnDefinition = "varchar(255) not null") - private String theme; + private String id; + /** + * Setting key. + */ @Column(name = "setting_key", columnDefinition = "varchar(255) not null") private String key; + /** + * Setting value + */ @Column(name = "setting_value", columnDefinition = "varchar(10239) not null") private String value; diff --git a/src/main/java/run/halo/app/model/enums/OptionSource.java b/src/main/java/run/halo/app/model/enums/OptionSource.java deleted file mode 100644 index b0b404ea3..000000000 --- a/src/main/java/run/halo/app/model/enums/OptionSource.java +++ /dev/null @@ -1,25 +0,0 @@ -package run.halo.app.model.enums; - -/** - * Option source. - * - * @author johnniang - * @date 4/1/19 - */ -@Deprecated -public enum OptionSource implements ValueEnum { - - SYSTEM(0), - THEME(1); - - private final int value; - - OptionSource(int value) { - this.value = value; - } - - @Override - public Integer getValue() { - return null; - } -} diff --git a/src/main/java/run/halo/app/model/enums/converter/OptionSourceConverter.java b/src/main/java/run/halo/app/model/enums/converter/OptionSourceConverter.java deleted file mode 100644 index a0e6de985..000000000 --- a/src/main/java/run/halo/app/model/enums/converter/OptionSourceConverter.java +++ /dev/null @@ -1,19 +0,0 @@ -package run.halo.app.model.enums.converter; - -import run.halo.app.model.enums.OptionSource; - -import javax.persistence.Converter; - -/** - * OptionSource converter. - * - * @author johnniang - * @date 4/1/19 - */ -@Converter(autoApply = true) -public class OptionSourceConverter extends AbstractConverter { - - public OptionSourceConverter() { - super(OptionSource.class); - } -} diff --git a/src/main/java/run/halo/app/model/support/Theme.java b/src/main/java/run/halo/app/model/support/Theme.java index f6238ff24..fdbad518e 100644 --- a/src/main/java/run/halo/app/model/support/Theme.java +++ b/src/main/java/run/halo/app/model/support/Theme.java @@ -11,6 +11,7 @@ import java.io.Serializable; * @date : 2018/1/3 */ @Data +@Deprecated public class Theme implements Serializable { private static final long serialVersionUID = 1L; @@ -28,5 +29,5 @@ public class Theme implements Serializable { /** * Theme properties */ - private ThemeProperties properties; + private ThemeProperty properties; } diff --git a/src/main/java/run/halo/app/model/support/ThemeProperties.java b/src/main/java/run/halo/app/model/support/ThemeProperties.java deleted file mode 100644 index acef5b928..000000000 --- a/src/main/java/run/halo/app/model/support/ThemeProperties.java +++ /dev/null @@ -1,27 +0,0 @@ -package run.halo.app.model.support; - -import lombok.Data; - -/** - * @author : RYAN0UP - * @date : 2019-03-22 - */ -@Data -public class ThemeProperties { - - private String id; - - private String name; - - private String website; - - private String description; - - private String logo; - - private String version; - - private String author; - - private String authorWebsite; -} diff --git a/src/main/java/run/halo/app/model/support/ThemeProperty.java b/src/main/java/run/halo/app/model/support/ThemeProperty.java new file mode 100644 index 000000000..f430d1622 --- /dev/null +++ b/src/main/java/run/halo/app/model/support/ThemeProperty.java @@ -0,0 +1,61 @@ +package run.halo.app.model.support; + +import lombok.Data; + +/** + * @author : RYAN0UP + * @date : 2019-03-22 + */ +@Data +public class ThemeProperty { + + /** + * Theme id. + */ + private String id; + + /** + * Theme name. + */ + private String name; + + /** + * Theme website. + */ + private String website; + + /** + * Theme description. + */ + private String description; + + /** + * Theme logo. + */ + private String logo; + + /** + * Theme version. + */ + private String version; + + /** + * Theme author. + */ + private String author; + + /** + * Theme author website. + */ + private String authorWebsite; + + /** + * Folder name. + */ + private String folderName; + + /** + * Has options. + */ + private Boolean hasOptions; +} diff --git a/src/main/java/run/halo/app/service/ThemeService.java b/src/main/java/run/halo/app/service/ThemeService.java index fa3cb5e05..78b4d2656 100644 --- a/src/main/java/run/halo/app/service/ThemeService.java +++ b/src/main/java/run/halo/app/service/ThemeService.java @@ -2,9 +2,8 @@ package run.halo.app.service; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; -import run.halo.app.model.support.Theme; import run.halo.app.model.support.ThemeFile; -import run.halo.app.model.support.ThemeProperties; +import run.halo.app.model.support.ThemeProperty; import java.io.File; import java.nio.file.Path; @@ -21,7 +20,7 @@ public interface ThemeService { * * @return list of themes */ - List getThemes(); + List getThemes(); /** * Lists theme folder by absolute path. @@ -78,14 +77,6 @@ public interface ThemeService { */ Path getBasePath(); - /** - * Get theme Properties. - * - * @param path path - * @return ThemeProperties - */ - ThemeProperties getProperties(@NonNull File path); - /** * Get template content by template absolute path. * @@ -130,11 +121,17 @@ public interface ThemeService { String render(@NonNull String pageName); /** - * Gets current theme name. + * Gets current theme id. * - * @return current theme name + * @return current theme id */ @NonNull - String getTheme(); + String getActivatedTheme(); + /** + * Actives a theme. + * + * @param themeId theme id must not be blank + */ + void activeTheme(@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 061d53844..c4577232e 100644 --- a/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java @@ -9,27 +9,30 @@ import cn.hutool.setting.dialect.Props; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import lombok.extern.slf4j.Slf4j; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Service; 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.NotFoundException; +import run.halo.app.exception.ServiceException; import run.halo.app.model.properties.PrimaryProperties; import run.halo.app.model.support.HaloConst; -import run.halo.app.model.support.Theme; import run.halo.app.model.support.ThemeFile; -import run.halo.app.model.support.ThemeProperties; +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; +import run.halo.app.utils.JsonUtils; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; import static run.halo.app.model.support.HaloConst.DEFAULT_THEME_NAME; @@ -42,39 +45,52 @@ import static run.halo.app.model.support.HaloConst.DEFAULT_THEME_NAME; public class ThemeServiceImpl implements ThemeService { /** - * The type of file that can be modified. + * Theme property file name. */ - private static String[] CAN_EDIT_SUFFIX = {"ftl", "css", "js"}; + private final static String THEME_PROPERTY_FILE_NAME = "theme.properties"; - /** - * These file names cannot be displayed. - */ - private static String[] FILTER_FILES = {".git", ".DS_Store", "theme.properties"}; - - /** - * Theme folder location. - */ - private final static String THEME_FOLDER = "templates/themes"; /** * Configuration file name. */ private final static String[] OPTIONS_NAMES = {"options.yaml", "options.yml"}; + /** + * The type of file that can be modified. + */ + private static String[] CAN_EDIT_SUFFIX = {"ftl", "css", "js"}; + + /** + * These file names cannot be displayed. + */ + private static String[] FILTER_FILES = {".git", ".DS_Store", THEME_PROPERTY_FILE_NAME, "options.yaml", "option.yml"}; + + /** + * Theme folder location. + */ + private final static String THEME_FOLDER = "templates/themes"; + + /** * Render template. */ private final static String RENDER_TEMPLATE = "themes/%s/%s"; + private final static String THEMES_CACHE_KEY = "themes"; + private final Path workDir; private final ObjectMapper yamlMapper; private final OptionService optionService; + private final StringCacheStore cacheStore; + public ThemeServiceImpl(HaloProperties haloProperties, - OptionService optionService) { + OptionService optionService, + StringCacheStore cacheStore) { this.optionService = optionService; + this.cacheStore = cacheStore; yamlMapper = new ObjectMapper(new YAMLFactory()); workDir = Paths.get(haloProperties.getWorkDir(), THEME_FOLDER); } @@ -85,36 +101,37 @@ public class ThemeServiceImpl implements ThemeService { * @return list of themes */ @Override - public List getThemes() { - final List themes = new ArrayList<>(); - final File[] files = getThemeBasePath().listFiles(); - try { - if (null != files) { - Theme theme; - for (File file : files) { - if (!file.isDirectory()) { - continue; - } - theme = new Theme(); - theme.setKey(file.getName()); - theme.setHasOptions(false); - for (String optionsName : OPTIONS_NAMES) { - // Resolve the options path - Path optionsPath = workDir.resolve(file.getName()).resolve(optionsName); - - if (!Files.exists(optionsPath)) { - continue; - } - theme.setHasOptions(true); - } - theme.setProperties(getProperties(new File(getThemeBasePath(), file.getName()))); - themes.add(theme); - } + public List getThemes() { + // Fetch themes from cache + return cacheStore.get(THEMES_CACHE_KEY).map(themesCache -> { + try { + @SuppressWarnings("unchecked") + List themes = JsonUtils.jsonToObject(themesCache, LinkedList.class); + return themes; + } catch (IOException e) { + throw new ServiceException("Failed to parse json", e); } - } catch (Exception e) { - throw new RuntimeException("Themes scan failed", e); - } - return themes; + }).orElseGet(() -> { + try { + + // List and filter sub folders + List themePaths = Files.list(getBasePath()).filter(path -> Files.isDirectory(path)).collect(Collectors.toList()); + + if (CollectionUtils.isEmpty(themePaths)) { + return Collections.emptyList(); + } + + // Get theme properties + List themes = themePaths.stream().map(this::getProperty).collect(Collectors.toList()); + + // Cache the themes + cacheStore.put(THEMES_CACHE_KEY, JsonUtils.objectToJson(themes)); + + return themes; + } catch (Exception e) { + throw new ServiceException("Themes scan failed", e); + } + }); } /** @@ -208,7 +225,7 @@ public class ThemeServiceImpl implements ThemeService { */ @Override public boolean isTemplateExist(String template) { - StrBuilder templatePath = new StrBuilder(getTheme()); + StrBuilder templatePath = new StrBuilder(getActivatedTheme()); templatePath.append("/"); templatePath.append(template); File file = new File(getThemeBasePath(), templatePath.toString()); @@ -242,30 +259,6 @@ public class ThemeServiceImpl implements ThemeService { return workDir; } - /** - * Get theme Properties. - * - * @param path path - * @return ThemeProperties - */ - @Override - public ThemeProperties getProperties(File path) { - File propertiesFile = new File(path, "theme.properties"); - ThemeProperties properties = new ThemeProperties(); - if (propertiesFile.exists()) { - Props props = new Props(propertiesFile); - properties.setId(props.getStr("theme.id")); - properties.setName(props.getStr("theme.name")); - properties.setWebsite(props.getStr("theme.website")); - properties.setDescription(props.getStr("theme.description")); - properties.setLogo(props.getStr("theme.logo")); - properties.setVersion(props.getStr("theme.version")); - properties.setAuthor(props.getStr("theme.author")); - properties.setAuthorWebsite(props.getStr("theme.author.website")); - } - return properties; - } - /** * Get template content by template absolute path. * @@ -297,12 +290,13 @@ public class ThemeServiceImpl implements ThemeService { */ @Override public void deleteTheme(String key) { - if (!this.isThemeExist(key)) { + if (!isThemeExist(key)) { throw new NotFoundException("该主题不存在!").setErrorData(key); } File file = new File(this.getThemeBasePath(), key); FileUtil.del(file); - HaloConst.THEMES = this.getThemes(); + + cacheStore.delete(THEMES_CACHE_KEY); } @Override @@ -334,12 +328,99 @@ public class ThemeServiceImpl implements ThemeService { @Override public String render(String pageName) { - return String.format(RENDER_TEMPLATE, getTheme(), pageName); + return String.format(RENDER_TEMPLATE, getActivatedTheme(), pageName); } @Override - public String getTheme() { + public String getActivatedTheme() { return optionService.getByProperty(PrimaryProperties.THEME).orElse(DEFAULT_THEME_NAME); } + @Override + public void activeTheme(String themeId) { + // TODO Check existence of the theme + } + + /** + * Gets theme property. + * + * @param themePath must not be null + * @return theme property + */ + 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; + } + + File propertyFile = propertyPath.toFile(); + + try { + Properties properties = new Properties(); + properties.load(new java.io.FileReader(propertyFile)); + + 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")); + themeProperty.setFolderName(propertyFile.getName()); + themeProperty.setHasOptions(hasOptions(propertyPath)); + + return themeProperty; + } catch (IOException e) { + // TODO Consider Ignore this error, then return null + throw new ServiceException("Failed to load: " + themePath.toString(), e); + } + } + + /** + * Gets theme Properties. + * + * @param path path + * @return ThemeProperty + */ + private ThemeProperty getProperties(File path) { + File propertiesFile = new File(path, "theme.properties"); + ThemeProperty properties = new ThemeProperty(); + if (propertiesFile.exists()) { + Props props = new Props(propertiesFile); + properties.setId(props.getStr("theme.id")); + properties.setName(props.getStr("theme.name")); + properties.setWebsite(props.getStr("theme.website")); + properties.setDescription(props.getStr("theme.description")); + properties.setLogo(props.getStr("theme.logo")); + properties.setVersion(props.getStr("theme.version")); + properties.setAuthor(props.getStr("theme.author")); + properties.setAuthorWebsite(props.getStr("theme.author.website")); + properties.setFolderName(path.getName()); + } + return properties; + } + + /** + * Check existence of the options. + * + * @param themePath theme path must not be null + * @return true if it has options; false otherwise + */ + private boolean hasOptions(@NonNull Path themePath) { + Assert.notNull(themePath, "Path must not be null"); + + for (String optionsName : OPTIONS_NAMES) { + // Resolve the options path + Path optionsPath = themePath.resolve(optionsName); + + if (Files.exists(optionsPath)) { + return true; + } + } + return false; + } } diff --git a/src/main/java/run/halo/app/service/impl/ThemeSettingServiceImpl.java b/src/main/java/run/halo/app/service/impl/ThemeSettingServiceImpl.java index a500292b0..1af45e494 100644 --- a/src/main/java/run/halo/app/service/impl/ThemeSettingServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/ThemeSettingServiceImpl.java @@ -22,5 +22,4 @@ public class ThemeSettingServiceImpl extends AbstractCrudService listAll() { + public List listAll() { return themeService.getThemes(); } @@ -55,7 +55,7 @@ public class ThemeController { */ @GetMapping("files") public List listFiles() { - return themeService.listThemeFolderBy(themeService.getTheme()); + return themeService.listThemeFolderBy(themeService.getActivatedTheme()); } @GetMapping("files/content") @@ -71,19 +71,21 @@ public class ThemeController { @GetMapping("files/custom") public List customTemplate() { - return themeService.getCustomTpl(themeService.getTheme()); + return themeService.getCustomTpl(themeService.getActivatedTheme()); } - @PostMapping("active") + @PostMapping("{themeId}/activate") @ApiOperation("Active a theme") - public void active(String theme) throws TemplateModelException { + public void active(@RequestParam("themeId") String themeId) throws TemplateModelException { + themeService.activeTheme(themeId); + // TODO Check existence of the theme - optionService.saveProperty(PrimaryProperties.THEME, theme); - configuration.setSharedVariable("themeName", theme); + optionService.saveProperty(PrimaryProperties.THEME, themeId); + configuration.setSharedVariable("themeName", themeId); configuration.setSharedVariable("options", optionService.listOptions()); } - @DeleteMapping("{key}") + @DeleteMapping("key/{key}") @ApiOperation("Deletes a theme") public void deleteBy(@PathVariable("key") String key) { themeService.deleteTheme(key); @@ -92,6 +94,6 @@ public class ThemeController { @GetMapping("configurations") @ApiOperation("Fetches theme configuration") public BaseResponse fetchConfig() { - return BaseResponse.ok(themeService.fetchConfig(themeService.getTheme())); + return BaseResponse.ok(themeService.fetchConfig(themeService.getActivatedTheme())); } } diff --git a/src/main/java/run/halo/app/web/controller/core/CommonController.java b/src/main/java/run/halo/app/web/controller/core/CommonController.java index 1dd025578..4773c697e 100644 --- a/src/main/java/run/halo/app/web/controller/core/CommonController.java +++ b/src/main/java/run/halo/app/web/controller/core/CommonController.java @@ -114,7 +114,7 @@ public class CommonController implements ErrorController { return "common/error/404"; } StrBuilder path = new StrBuilder("themes/"); - path.append(themeService.getTheme()); + path.append(themeService.getActivatedTheme()); path.append("/404"); return path.toString(); } @@ -130,7 +130,7 @@ public class CommonController implements ErrorController { return "common/error/500"; } StrBuilder path = new StrBuilder("themes/"); - path.append(themeService.getTheme()); + path.append(themeService.getActivatedTheme()); path.append("/500"); return path.toString(); } diff --git a/src/main/java/run/halo/app/web/controller/core/InstallController.java b/src/main/java/run/halo/app/web/controller/core/InstallController.java index 4c8216f51..a81088090 100644 --- a/src/main/java/run/halo/app/web/controller/core/InstallController.java +++ b/src/main/java/run/halo/app/web/controller/core/InstallController.java @@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.ResponseBody; import run.halo.app.exception.BadRequestException; import run.halo.app.model.entity.*; import run.halo.app.model.enums.AttachmentType; -import run.halo.app.model.enums.OptionSource; import run.halo.app.model.params.InstallParam; import run.halo.app.model.properties.*; import run.halo.app.model.support.BaseResponse;