diff --git a/src/main/java/run/halo/app/controller/admin/api/BackupController.java b/src/main/java/run/halo/app/controller/admin/api/BackupController.java index 08441ea358..c6221854a7 100644 --- a/src/main/java/run/halo/app/controller/admin/api/BackupController.java +++ b/src/main/java/run/halo/app/controller/admin/api/BackupController.java @@ -6,8 +6,12 @@ import io.swagger.annotations.ApiOperation; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.Resource; @@ -81,8 +85,19 @@ public BackupDTO getMarkdownBackup(@RequestParam("filename") String filename) { @PostMapping("work-dir") @ApiOperation("Backups work directory") @DisableOnCondition - public BackupDTO backupHalo() { - return backupService.backupWorkDirectory(); + public BackupDTO backupHalo(@RequestBody List options) { + return backupService.backupWorkDirectory(options); + } + + @GetMapping("work-dir/options") + @ApiOperation("Gets items that can be backed up") + public List listBackupItems() throws IOException { + return Files.list(Paths.get(haloProperties.getWorkDir())) + .map(Path::getFileName) + .filter(Objects::nonNull) + .map(Path::toString) + .sorted() + .collect(Collectors.toList()); } @GetMapping("work-dir") diff --git a/src/main/java/run/halo/app/service/BackupService.java b/src/main/java/run/halo/app/service/BackupService.java index 6ee5ef6400..d684b2df84 100644 --- a/src/main/java/run/halo/app/service/BackupService.java +++ b/src/main/java/run/halo/app/service/BackupService.java @@ -32,11 +32,11 @@ public interface BackupService { /** * Zips work directory. * + * @param options file or directory items to back up * @return backup dto. */ @NonNull - BackupDTO backupWorkDirectory(); - + BackupDTO backupWorkDirectory(List options); /** * Lists all backups. diff --git a/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java b/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java index 9562eaff0b..7024eece9c 100644 --- a/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java @@ -33,10 +33,12 @@ import org.springframework.lang.NonNull; import org.springframework.stereotype.Service; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; import run.halo.app.config.properties.HaloProperties; import run.halo.app.event.options.OptionUpdatedEvent; import run.halo.app.event.theme.ThemeUpdatedEvent; +import run.halo.app.exception.BadRequestException; import run.halo.app.exception.NotFoundException; import run.halo.app.exception.ServiceException; import run.halo.app.handler.file.FileHandler; @@ -213,7 +215,10 @@ public BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException { } @Override - public BackupDTO backupWorkDirectory() { + public BackupDTO backupWorkDirectory(List options) { + if (CollectionUtils.isEmpty(options)) { + throw new BadRequestException("The options parameter is missing, at least one."); + } // Zip work directory to temporary file try { // Create zip path for halo zip @@ -229,7 +234,17 @@ public BackupDTO backupWorkDirectory() { // Zip halo run.halo.app.utils.FileUtils - .zip(Paths.get(this.haloProperties.getWorkDir()), haloZipPath); + .zip(Paths.get(this.haloProperties.getWorkDir()), haloZipPath, + path -> { + for (String itemToBackup : options) { + Path backupItemPath = + Paths.get(this.haloProperties.getWorkDir()).resolve(itemToBackup); + if (path.startsWith(backupItemPath)) { + return true; + } + } + return false; + }); // Build backup dto return buildBackupDto(BACKUP_RESOURCE_BASE_URI, haloZipPath); diff --git a/src/main/java/run/halo/app/utils/FileUtils.java b/src/main/java/run/halo/app/utils/FileUtils.java index 9e8b0981be..e23279b6b1 100644 --- a/src/main/java/run/halo/app/utils/FileUtils.java +++ b/src/main/java/run/halo/app/utils/FileUtils.java @@ -185,6 +185,23 @@ public static void zip(@NonNull Path pathToZip, @NonNull Path pathOfArchive) } } + /** + * Zips folder or file with filter. + * + * @param pathToZip file path to zip must not be null + * @param pathOfArchive zip file path to archive must not be null + * @param filter folder or file filter + * @throws IOException throws when failed to access file to be zipped + */ + public static void zip(@NonNull Path pathToZip, @NonNull Path pathOfArchive, + @Nullable Predicate filter) throws IOException { + try (OutputStream outputStream = Files.newOutputStream(pathOfArchive)) { + try (ZipOutputStream zipOut = new ZipOutputStream(outputStream)) { + zip(pathToZip, zipOut, filter); + } + } + } + /** * Zips folder or file. * @@ -198,6 +215,20 @@ public static void zip(@NonNull Path pathToZip, @NonNull ZipOutputStream zipOut) zip(pathToZip, pathToZip.getFileName().toString(), zipOut); } + /** + * Zips folder or file with filter. + * + * @param pathToZip file path to zip must not be null + * @param zipOut zip output stream must not be null + * @param filter directory or file filter + * @throws IOException throws when failed to access file to be zipped + */ + public static void zip(@NonNull Path pathToZip, @NonNull ZipOutputStream zipOut, + Predicate filter) throws IOException { + // Zip file + zip(pathToZip, pathToZip.getFileName().toString(), zipOut, filter); + } + /** * Zips folder or file. * @@ -208,6 +239,20 @@ public static void zip(@NonNull Path pathToZip, @NonNull ZipOutputStream zipOut) */ private static void zip(@NonNull Path fileToZip, @NonNull String fileName, @NonNull ZipOutputStream zipOut) throws IOException { + zip(fileToZip, fileName, zipOut, null); + } + + /** + * Zips folder or file with path filter. + * + * @param fileToZip file path to zip must not be null + * @param fileName file name must not be blank + * @param zipOut zip output stream must not be null + * @param filter directory or file filter + * @throws IOException throws when failed to access file to be zipped + */ + private static void zip(@NonNull Path fileToZip, @NonNull String fileName, + @NonNull ZipOutputStream zipOut, @Nullable Predicate filter) throws IOException { if (Files.isDirectory(fileToZip)) { log.debug("Try to zip folder: [{}]", fileToZip); // Append with '/' if missing @@ -222,10 +267,12 @@ private static void zip(@NonNull Path fileToZip, @NonNull String fileName, try (Stream subPathStream = Files.list(fileToZip)) { // There should not use foreach for stream as internal zip method will throw // IOException - List subFiles = subPathStream.collect(Collectors.toList()); + List subFiles = + filter != null ? subPathStream.filter(filter).collect(Collectors.toList()) + : subPathStream.collect(Collectors.toList()); for (Path subFileToZip : subFiles) { // Zip children - zip(subFileToZip, folderName + subFileToZip.getFileName(), zipOut); + zip(subFileToZip, folderName + subFileToZip.getFileName(), zipOut, filter); } } } else {