diff --git a/src/main/java/run/halo/app/listener/StartedListener.java b/src/main/java/run/halo/app/listener/StartedListener.java index abd233f5d..e56bac67b 100644 --- a/src/main/java/run/halo/app/listener/StartedListener.java +++ b/src/main/java/run/halo/app/listener/StartedListener.java @@ -107,7 +107,7 @@ public class StartedListener implements ApplicationListener { @NonNull Locale getLocale(); + /** + * Gets blog base url. (Without /) + * + * @return blog base url (If blog url isn't present, current machine IP address will be default) + */ + @NonNull + String getBlogBaseUrl(); + } diff --git a/src/main/java/run/halo/app/service/ThemeService.java b/src/main/java/run/halo/app/service/ThemeService.java index 3841e54e6..4c225c2f2 100644 --- a/src/main/java/run/halo/app/service/ThemeService.java +++ b/src/main/java/run/halo/app/service/ThemeService.java @@ -42,6 +42,11 @@ public interface ThemeService { */ String THEME_FOLDER = "templates/themes"; + /** + * Theme screenshots name. + */ + String THEME_SCREENSHOTS_NAME = "screenshot"; + /** * Render template. diff --git a/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java b/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java index 40cce4c89..bbdd0d139 100644 --- a/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java @@ -1,8 +1,10 @@ package run.halo.app.service.impl; +import cn.hutool.core.util.StrUtil; import com.qiniu.common.Zone; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -15,6 +17,7 @@ import run.halo.app.model.properties.*; import run.halo.app.repository.OptionRepository; import run.halo.app.service.OptionService; import run.halo.app.service.base.AbstractCrudService; +import run.halo.app.utils.HaloUtils; import run.halo.app.utils.ServiceUtils; import java.util.List; @@ -35,9 +38,13 @@ public class OptionServiceImpl extends AbstractCrudService impl private final OptionRepository optionRepository; - public OptionServiceImpl(OptionRepository optionRepository) { + private final ApplicationContext applicationContext; + + public OptionServiceImpl(OptionRepository optionRepository, + ApplicationContext applicationContext) { super(optionRepository); this.optionRepository = optionRepository; + this.applicationContext = applicationContext; } @Override @@ -266,4 +273,20 @@ public class OptionServiceImpl extends AbstractCrudService impl } }).orElseGet(Locale::getDefault); } + + @Override + public String getBlogBaseUrl() { + // Get server port + String serverPort = applicationContext.getEnvironment().getProperty("server.port", "8080"); + + String blogUrl = getByPropertyOfNullable(BlogProperties.BLOG_URL); + + if (StrUtil.isNotBlank(blogUrl)) { + blogUrl = StrUtil.removeSuffix(blogUrl, "/"); + } else { + blogUrl = String.format("http://%s:%s", HaloUtils.getMachineIP(), serverPort); + } + + return blogUrl; + } } 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 907d4e65e..921ec23ae 100644 --- a/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import freemarker.template.Configuration; import freemarker.template.TemplateModelException; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; @@ -16,6 +17,7 @@ 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; @@ -25,6 +27,7 @@ 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; import run.halo.app.utils.JsonUtils; import java.io.File; @@ -52,6 +55,11 @@ public class ThemeServiceImpl implements ThemeService { */ private final Path workDir; + /** + * Activated theme id. + */ + private String activatedThemeId; + private final ObjectMapper yamlMapper; private final OptionService optionService; @@ -199,6 +207,11 @@ public class ThemeServiceImpl implements ThemeService { // Get the theme property ThemeProperty themeProperty = getThemeOfNonNullBy(themeId); + if (themeId.equals(getActivatedThemeId())) { + // Prevent to delete the activated theme + throw new BadRequestException("You can't delete the activated theme").setErrorData(themeId); + } + try { // Delete the folder Files.deleteIfExists(Paths.get(themeProperty.getThemePath())); @@ -217,7 +230,7 @@ public class ThemeServiceImpl implements ThemeService { // Get theme property ThemeProperty themeProperty = getThemeOfNonNullBy(themeId); - if (!themeProperty.getHasOptions()) { + if (!themeProperty.isHasOptions()) { // If this theme dose not has an option, then return null return null; } @@ -252,7 +265,24 @@ public class ThemeServiceImpl implements ThemeService { @Override public String getActivatedThemeId() { - return optionService.getByProperty(PrimaryProperties.THEME).orElse(DEFAULT_THEME_NAME); + if (StringUtils.isBlank(activatedThemeId)) { + synchronized (this) { + if (StringUtils.isBlank(activatedThemeId)) { + return optionService.getByProperty(PrimaryProperties.THEME).orElse(DEFAULT_THEME_NAME); + } + } + } + + return activatedThemeId; + } + + /** + * Set activated theme id. + * + * @param themeId theme id + */ + private void setActivatedThemeId(@Nullable String themeId) { + this.activatedThemeId = themeId; } @Override @@ -263,6 +293,10 @@ public class ThemeServiceImpl implements ThemeService { // Save the theme to database optionService.saveProperty(PrimaryProperties.THEME, themeId); + + // Set the activated theme id + setActivatedThemeId(themeId); + try { // TODO Refactor here in the future configuration.setSharedVariable("themeName", themeId); @@ -388,8 +422,23 @@ public class ThemeServiceImpl implements ThemeService { themeProperty.setVersion(properties.getProperty("theme.version")); themeProperty.setAuthor(properties.getProperty("theme.author")); themeProperty.setAuthorWebsite(properties.getProperty("theme.author.website")); + themeProperty.setThemePath(themePath.toString()); themeProperty.setHasOptions(hasOptions(themePath)); + themeProperty.setActivated(false); + + // Set screenshots + getScreenshotsFileName(themePath).ifPresent(screenshotsName -> + themeProperty.setScreenshots(StringUtils.join(optionService.getBlogBaseUrl(), + "/", + FilenameUtils.getBasename(themeProperty.getThemePath()), + "/", + screenshotsName))); + + if (themeProperty.getId().equals(getActivatedThemeId())) { + // Set activation + themeProperty.setActivated(true); + } return themeProperty; } catch (IOException e) { @@ -398,6 +447,24 @@ public class ThemeServiceImpl implements ThemeService { } } + /** + * Gets screenshots file name. + * + * @param themePath theme path must not be null + * @return screenshots file name or null if the given theme path has not screenshots + * @throws IOException throws when listing files + */ + private Optional getScreenshotsFileName(@NonNull Path themePath) throws IOException { + Assert.notNull(themePath, "Theme path must not be null"); + + return Files.list(themePath) + .filter(path -> Files.isRegularFile(path) + && Files.isReadable(path) + && FilenameUtils.getBasename(path.toString()).equalsIgnoreCase(THEME_SCREENSHOTS_NAME)) + .findFirst() + .map(path -> path.getFileName().toString()); + } + /** * Check existence of the options. * diff --git a/src/main/java/run/halo/app/web/controller/admin/api/ThemeController.java b/src/main/java/run/halo/app/web/controller/admin/api/ThemeController.java index 0dda8b023..f37a959d7 100644 --- a/src/main/java/run/halo/app/web/controller/admin/api/ThemeController.java +++ b/src/main/java/run/halo/app/web/controller/admin/api/ThemeController.java @@ -67,7 +67,7 @@ public class ThemeController { @PostMapping("{themeId}/activation") @ApiOperation("Activates a theme") - public ThemeProperty active(@RequestParam("themeId") String themeId) { + public ThemeProperty active(@PathVariable("themeId") String themeId) { return themeService.activeTheme(themeId); } diff --git a/src/test/java/run/halo/app/utils/PathsTest.java b/src/test/java/run/halo/app/utils/PathsTest.java index 7173d2b83..030fb3111 100644 --- a/src/test/java/run/halo/app/utils/PathsTest.java +++ b/src/test/java/run/halo/app/utils/PathsTest.java @@ -1,5 +1,6 @@ package run.halo.app.utils; +import io.micrometer.core.annotation.TimedSet; import org.junit.Test; import java.nio.file.Path; @@ -7,6 +8,7 @@ import java.nio.file.Paths; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; /** * Paths test. @@ -22,5 +24,14 @@ public class PathsTest { Path path = Paths.get("/home/test/", "/upload/test.txt"); assertThat(path.toString(), equalTo("/home/test/upload/test.txt")); assertThat(path.getParent().toString(), equalTo("/home/test/upload")); + assertThat(path.getFileName().toString(), equalTo("test.txt")); + } + + @Test + public void startWithTest() { + Path path = Paths.get("/test/test.txt"); + assertThat(path.getFileName().toString(), equalTo("test.txt")); + boolean isStartWith = FilenameUtils.getBasename(path.toString()).equalsIgnoreCase("test"); + assertTrue(isStartWith); } }