mirror of https://github.com/halo-dev/halo
Enhance ThemeService
parent
9b3bdc8d5e
commit
e098e44d58
|
@ -3,6 +3,7 @@ package run.halo.app.cache;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import javax.annotation.PreDestroy;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
@ -28,6 +29,8 @@ public class InMemoryCacheStore extends StringCacheStore {
|
||||||
*/
|
*/
|
||||||
private final static ConcurrentHashMap<String, CacheWrapper<String>> cacheContainer = new ConcurrentHashMap<>();
|
private final static ConcurrentHashMap<String, CacheWrapper<String>> cacheContainer = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Timer timer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lock.
|
* Lock.
|
||||||
*/
|
*/
|
||||||
|
@ -35,7 +38,8 @@ public class InMemoryCacheStore extends StringCacheStore {
|
||||||
|
|
||||||
public InMemoryCacheStore() {
|
public InMemoryCacheStore() {
|
||||||
// Run a cache store cleaner
|
// Run a cache store cleaner
|
||||||
new Timer().scheduleAtFixedRate(new CacheExpiryCleaner(), 0, PERIOD);
|
timer = new Timer();
|
||||||
|
timer.scheduleAtFixedRate(new CacheExpiryCleaner(), 0, PERIOD);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -106,4 +110,10 @@ public class InMemoryCacheStore extends StringCacheStore {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void preDestroy() {
|
||||||
|
log.debug("Cancelling all timer tasks");
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import run.halo.app.model.support.ThemeProperty;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author : RYAN0UP
|
* @author : RYAN0UP
|
||||||
|
@ -15,6 +16,24 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public interface ThemeService {
|
public interface ThemeService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get theme property by theme id.
|
||||||
|
*
|
||||||
|
* @param themeId must not be blank
|
||||||
|
* @return theme property
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
ThemeProperty getThemeOfNonNullBy(@NonNull String themeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get theme property by theme id.
|
||||||
|
*
|
||||||
|
* @param themeId must not be blank
|
||||||
|
* @return a optional theme property
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Optional<ThemeProperty> getThemeBy(@NonNull String themeId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all themes
|
* Gets all themes
|
||||||
*
|
*
|
||||||
|
@ -58,10 +77,10 @@ public interface ThemeService {
|
||||||
/**
|
/**
|
||||||
* Judging whether theme exists under template path
|
* Judging whether theme exists under template path
|
||||||
*
|
*
|
||||||
* @param theme theme name
|
* @param themeId theme name
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
boolean isThemeExist(@NonNull String theme);
|
boolean isThemeExist(@NonNull String themeId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets theme base path.
|
* Gets theme base path.
|
||||||
|
@ -83,7 +102,6 @@ public interface ThemeService {
|
||||||
* @param absolutePath absolute path
|
* @param absolutePath absolute path
|
||||||
* @return template content
|
* @return template content
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
String getTemplateContent(@NonNull String absolutePath);
|
String getTemplateContent(@NonNull String absolutePath);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,7 +5,6 @@ import cn.hutool.core.io.file.FileReader;
|
||||||
import cn.hutool.core.io.file.FileWriter;
|
import cn.hutool.core.io.file.FileWriter;
|
||||||
import cn.hutool.core.text.StrBuilder;
|
import cn.hutool.core.text.StrBuilder;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.setting.dialect.Props;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -15,6 +14,7 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import run.halo.app.cache.StringCacheStore;
|
import run.halo.app.cache.StringCacheStore;
|
||||||
import run.halo.app.config.properties.HaloProperties;
|
import run.halo.app.config.properties.HaloProperties;
|
||||||
|
import run.halo.app.exception.ForbiddenException;
|
||||||
import run.halo.app.exception.NotFoundException;
|
import run.halo.app.exception.NotFoundException;
|
||||||
import run.halo.app.exception.ServiceException;
|
import run.halo.app.exception.ServiceException;
|
||||||
import run.halo.app.model.properties.PrimaryProperties;
|
import run.halo.app.model.properties.PrimaryProperties;
|
||||||
|
@ -28,6 +28,8 @@ import run.halo.app.utils.JsonUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
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;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
@ -78,6 +80,9 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
|
|
||||||
private final static String THEMES_CACHE_KEY = "themes";
|
private final static String THEMES_CACHE_KEY = "themes";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme work directory.
|
||||||
|
*/
|
||||||
private final Path workDir;
|
private final Path workDir;
|
||||||
|
|
||||||
private final ObjectMapper yamlMapper;
|
private final ObjectMapper yamlMapper;
|
||||||
|
@ -95,6 +100,23 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
workDir = Paths.get(haloProperties.getWorkDir(), THEME_FOLDER);
|
workDir = Paths.get(haloProperties.getWorkDir(), THEME_FOLDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ThemeProperty getThemeOfNonNullBy(String themeId) {
|
||||||
|
return getThemeBy(themeId).orElseThrow(() -> new NotFoundException("Theme with id: " + themeId + " was not found").setErrorData(themeId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ThemeProperty> getThemeBy(String themeId) {
|
||||||
|
Assert.hasText(themeId, "Theme id must not be blank");
|
||||||
|
|
||||||
|
List<ThemeProperty> themes = getThemes();
|
||||||
|
|
||||||
|
log.debug("Themes type: [{}]", themes.getClass());
|
||||||
|
log.debug("Themes: [{}]", themes);
|
||||||
|
|
||||||
|
return themes.stream().filter(themeProperty -> themeProperty.getId().equals(themeId)).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all themes
|
* Gets all themes
|
||||||
*
|
*
|
||||||
|
@ -105,15 +127,14 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
// Fetch themes from cache
|
// Fetch themes from cache
|
||||||
return cacheStore.get(THEMES_CACHE_KEY).map(themesCache -> {
|
return cacheStore.get(THEMES_CACHE_KEY).map(themesCache -> {
|
||||||
try {
|
try {
|
||||||
@SuppressWarnings("unchecked")
|
// Convert to theme properties
|
||||||
List<ThemeProperty> themes = JsonUtils.jsonToObject(themesCache, LinkedList.class);
|
ThemeProperty[] themeProperties = JsonUtils.jsonToObject(themesCache, ThemeProperty[].class);
|
||||||
return themes;
|
return Arrays.asList(themeProperties);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ServiceException("Failed to parse json", e);
|
throw new ServiceException("Failed to parse json", e);
|
||||||
}
|
}
|
||||||
}).orElseGet(() -> {
|
}).orElseGet(() -> {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// List and filter sub folders
|
// List and filter sub folders
|
||||||
List<Path> themePaths = Files.list(getBasePath()).filter(path -> Files.isDirectory(path)).collect(Collectors.toList());
|
List<Path> themePaths = Files.list(getBasePath()).filter(path -> Files.isDirectory(path)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
@ -142,6 +163,9 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<ThemeFile> listThemeFolder(String absolutePath) {
|
public List<ThemeFile> listThemeFolder(String absolutePath) {
|
||||||
|
// Check this path
|
||||||
|
checkDirectory(absolutePath);
|
||||||
|
|
||||||
final List<ThemeFile> templates = new ArrayList<>();
|
final List<ThemeFile> templates = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
File absolutePathFile = new File(absolutePath);
|
File absolutePathFile = new File(absolutePath);
|
||||||
|
@ -235,12 +259,13 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
/**
|
/**
|
||||||
* Judging whether theme exists under template path
|
* Judging whether theme exists under template path
|
||||||
*
|
*
|
||||||
* @param theme theme name
|
* @param themeId theme name
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isThemeExist(String theme) {
|
public boolean isThemeExist(String themeId) {
|
||||||
File file = new File(getThemeBasePath(), theme);
|
// TODO Get theme folder name by theme id
|
||||||
|
File file = new File(getThemeBasePath(), themeId);
|
||||||
return file.exists();
|
return file.exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,8 +292,10 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getTemplateContent(String absolutePath) {
|
public String getTemplateContent(String absolutePath) {
|
||||||
final FileReader fileReader = new FileReader(absolutePath);
|
// Check the path
|
||||||
return fileReader.readString();
|
checkDirectory(absolutePath);
|
||||||
|
|
||||||
|
return new FileReader(absolutePath).readString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -341,6 +368,22 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
// TODO Check existence of the theme
|
// TODO Check existence of the theme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if directory is valid or not.
|
||||||
|
*
|
||||||
|
* @param absoluteName must not be blank
|
||||||
|
* @throws ForbiddenException throws when the given absolute directory name is invalid
|
||||||
|
*/
|
||||||
|
private void checkDirectory(@NonNull String absoluteName) {
|
||||||
|
Assert.hasText(absoluteName, "Absolute name must not be blank");
|
||||||
|
|
||||||
|
boolean valid = Paths.get(absoluteName).startsWith(workDir);
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
throw new ForbiddenException("You cannot access " + absoluteName).setErrorData(absoluteName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets theme property.
|
* Gets theme property.
|
||||||
*
|
*
|
||||||
|
@ -355,11 +398,10 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
File propertyFile = propertyPath.toFile();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
properties.load(new java.io.FileReader(propertyFile));
|
// Load properties
|
||||||
|
properties.load(new InputStreamReader(Files.newInputStream(propertyPath), StandardCharsets.UTF_8));
|
||||||
|
|
||||||
ThemeProperty themeProperty = new ThemeProperty();
|
ThemeProperty themeProperty = new ThemeProperty();
|
||||||
themeProperty.setId(properties.getProperty("theme.id"));
|
themeProperty.setId(properties.getProperty("theme.id"));
|
||||||
|
@ -370,40 +412,16 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
themeProperty.setVersion(properties.getProperty("theme.version"));
|
themeProperty.setVersion(properties.getProperty("theme.version"));
|
||||||
themeProperty.setAuthor(properties.getProperty("theme.author"));
|
themeProperty.setAuthor(properties.getProperty("theme.author"));
|
||||||
themeProperty.setAuthorWebsite(properties.getProperty("theme.author.website"));
|
themeProperty.setAuthorWebsite(properties.getProperty("theme.author.website"));
|
||||||
themeProperty.setFolderName(propertyFile.getName());
|
themeProperty.setFolderName(themePath.getFileName().toString());
|
||||||
themeProperty.setHasOptions(hasOptions(propertyPath));
|
themeProperty.setHasOptions(hasOptions(propertyPath));
|
||||||
|
|
||||||
return themeProperty;
|
return themeProperty;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// TODO Consider Ignore this error, then return null
|
// TODO Consider to ignore this error, then return null
|
||||||
throw new ServiceException("Failed to load: " + themePath.toString(), e);
|
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.
|
* Check existence of the options.
|
||||||
*
|
*
|
||||||
|
@ -421,6 +439,7 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,12 @@ public class ThemeController {
|
||||||
this.themeService = themeService;
|
this.themeService = themeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("{themeId}")
|
||||||
|
@ApiOperation("Gets theme property by theme id")
|
||||||
|
public ThemeProperty getBy(@PathVariable("themeId") String themeId) {
|
||||||
|
return themeService.getThemeOfNonNullBy(themeId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all themes
|
* List all themes
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue