mirror of https://github.com/halo-dev/halo
Provide backup dto fetch api (#1278)
* Fix swagger security reference config error * Add backup dto fetch api * Rearrange fetch apipull/1279/head
parent
49a461f245
commit
b57712e23e
|
@ -146,8 +146,8 @@ public class SwaggerConfiguration {
|
|||
|
||||
private List<SecurityScheme> adminApiKeys() {
|
||||
return Arrays.asList(
|
||||
new ApiKey("Token from header", ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()),
|
||||
new ApiKey("Token from query", ADMIN_TOKEN_QUERY_NAME, In.QUERY.name())
|
||||
new ApiKey(ADMIN_TOKEN_HEADER_NAME, ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()),
|
||||
new ApiKey(ADMIN_TOKEN_QUERY_NAME, ADMIN_TOKEN_QUERY_NAME, In.QUERY.name())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ public class SwaggerConfiguration {
|
|||
final PathMatcher pathMatcher = new AntPathMatcher();
|
||||
return Collections.singletonList(
|
||||
SecurityContext.builder()
|
||||
.securityReferences(defaultAuth())
|
||||
.securityReferences(adminApiAuths())
|
||||
.operationSelector(operationContext -> {
|
||||
var requestMappingPattern = operationContext.requestMappingPattern();
|
||||
return pathMatcher.match("/api/admin/**/*", requestMappingPattern);
|
||||
|
@ -166,8 +166,8 @@ public class SwaggerConfiguration {
|
|||
|
||||
private List<SecurityScheme> contentApiKeys() {
|
||||
return Arrays.asList(
|
||||
new ApiKey("Access key from header", API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()),
|
||||
new ApiKey("Access key from query", API_ACCESS_KEY_QUERY_NAME, In.QUERY.name())
|
||||
new ApiKey(API_ACCESS_KEY_HEADER_NAME, API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()),
|
||||
new ApiKey(API_ACCESS_KEY_QUERY_NAME, API_ACCESS_KEY_QUERY_NAME, In.QUERY.name())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,7 @@ public class SwaggerConfiguration {
|
|||
final PathMatcher pathMatcher = new AntPathMatcher();
|
||||
return Collections.singletonList(
|
||||
SecurityContext.builder()
|
||||
.securityReferences(contentApiAuth())
|
||||
.securityReferences(contentApiAuths())
|
||||
.operationSelector(operationContext -> {
|
||||
var requestMappingPattern = operationContext.requestMappingPattern();
|
||||
return pathMatcher.match("/api/content/**/*", requestMappingPattern);
|
||||
|
@ -184,18 +184,18 @@ public class SwaggerConfiguration {
|
|||
);
|
||||
}
|
||||
|
||||
private List<SecurityReference> defaultAuth() {
|
||||
private List<SecurityReference> adminApiAuths() {
|
||||
AuthorizationScope[] authorizationScopes =
|
||||
{new AuthorizationScope("Admin api", "Access admin api")};
|
||||
return Arrays.asList(new SecurityReference("Token from header", authorizationScopes),
|
||||
new SecurityReference("Token from query", authorizationScopes));
|
||||
return Arrays.asList(new SecurityReference(ADMIN_TOKEN_HEADER_NAME, authorizationScopes),
|
||||
new SecurityReference(ADMIN_TOKEN_QUERY_NAME, authorizationScopes));
|
||||
}
|
||||
|
||||
private List<SecurityReference> contentApiAuth() {
|
||||
private List<SecurityReference> contentApiAuths() {
|
||||
AuthorizationScope[] authorizationScopes =
|
||||
{new AuthorizationScope("content api", "Access content api")};
|
||||
return Arrays.asList(new SecurityReference("Access key from header", authorizationScopes),
|
||||
new SecurityReference("Access key from query", authorizationScopes));
|
||||
return Arrays.asList(new SecurityReference(API_ACCESS_KEY_HEADER_NAME, authorizationScopes),
|
||||
new SecurityReference(API_ACCESS_KEY_QUERY_NAME, authorizationScopes));
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static run.halo.app.service.BackupService.BackupType.JSON_DATA;
|
||||
import static run.halo.app.service.BackupService.BackupType.MARKDOWN;
|
||||
import static run.halo.app.service.BackupService.BackupType.WHOLE_SITE;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -21,6 +26,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.annotation.DisableOnCondition;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.model.dto.BackupDTO;
|
||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||
import run.halo.app.model.params.PostMarkdownParam;
|
||||
|
@ -48,6 +54,29 @@ public class BackupController {
|
|||
this.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
@GetMapping("work-dir/fetch")
|
||||
public BackupDTO getWorkDirBackup(@RequestParam("filename") String filename) {
|
||||
return backupService.getBackup(Paths.get(haloProperties.getWorkDir(), filename), WHOLE_SITE)
|
||||
.orElseThrow(() ->
|
||||
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
|
||||
}
|
||||
|
||||
@GetMapping("data/fetch")
|
||||
public BackupDTO getDataBackup(@RequestParam("filename") String filename) {
|
||||
return backupService
|
||||
.getBackup(Paths.get(haloProperties.getDataExportDir(), filename), JSON_DATA)
|
||||
.orElseThrow(() ->
|
||||
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
|
||||
}
|
||||
|
||||
@GetMapping("markdown/fetch")
|
||||
public BackupDTO getMarkdownBackup(@RequestParam("filename") String filename) {
|
||||
return backupService
|
||||
.getBackup(Paths.get(haloProperties.getBackupMarkdownDir(), filename), MARKDOWN)
|
||||
.orElseThrow(() ->
|
||||
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
|
||||
}
|
||||
|
||||
@PostMapping("work-dir")
|
||||
@ApiOperation("Backups work directory")
|
||||
@DisableOnCondition
|
||||
|
@ -61,16 +90,16 @@ public class BackupController {
|
|||
return backupService.listWorkDirBackups();
|
||||
}
|
||||
|
||||
@GetMapping("work-dir/{fileName:.+}")
|
||||
@GetMapping("work-dir/{filename:.+}")
|
||||
@ApiOperation("Downloads a work directory backup file")
|
||||
@DisableOnCondition
|
||||
public ResponseEntity<Resource> downloadBackup(@PathVariable("fileName") String fileName,
|
||||
public ResponseEntity<Resource> downloadBackup(@PathVariable("filename") String filename,
|
||||
HttpServletRequest request) {
|
||||
log.info("Try to download backup file: [{}]", fileName);
|
||||
log.info("Trying to download backup file: [{}]", filename);
|
||||
|
||||
// Load file as resource
|
||||
Resource backupResource =
|
||||
backupService.loadFileAsResource(haloProperties.getBackupDir(), fileName);
|
||||
backupService.loadFileAsResource(haloProperties.getBackupDir(), filename);
|
||||
|
||||
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||
// Try to determine file's content type
|
||||
|
|
|
@ -252,7 +252,8 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
|
|||
|
||||
// Get allowed uri
|
||||
String allowedUri = oneTimeTokenService.get(oneTimeToken)
|
||||
.orElseThrow(() -> new BadRequestException("The one-time token does not exist")
|
||||
.orElseThrow(() -> new BadRequestException(
|
||||
"The one-time token does not exist or has been expired")
|
||||
.setErrorData(oneTimeToken));
|
||||
|
||||
// Get request uri
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.security.service.impl;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -16,10 +17,9 @@ import run.halo.app.utils.HaloUtils;
|
|||
@Service
|
||||
public class OneTimeTokenServiceImpl implements OneTimeTokenService {
|
||||
|
||||
/**
|
||||
* One-time token expired day. (unit: day)
|
||||
*/
|
||||
public static final int OTT_EXPIRED_DAY = 1;
|
||||
private static final String tokenPrefix = "OTT-";
|
||||
|
||||
private static final Duration OTT_EXPIRATION_TIME = Duration.ofMinutes(5);
|
||||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
|
@ -32,7 +32,7 @@ public class OneTimeTokenServiceImpl implements OneTimeTokenService {
|
|||
Assert.hasText(oneTimeToken, "One-time token must not be blank");
|
||||
|
||||
// Get from cache store
|
||||
return cacheStore.get(oneTimeToken);
|
||||
return cacheStore.get(tokenPrefix + oneTimeToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -43,7 +43,10 @@ public class OneTimeTokenServiceImpl implements OneTimeTokenService {
|
|||
String oneTimeToken = HaloUtils.randomUUIDWithoutDash();
|
||||
|
||||
// Put ott along with request uri
|
||||
cacheStore.put(oneTimeToken, uri, OTT_EXPIRED_DAY, TimeUnit.DAYS);
|
||||
cacheStore.put(tokenPrefix + oneTimeToken,
|
||||
uri,
|
||||
OTT_EXPIRATION_TIME.getSeconds(),
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
// Return ott
|
||||
return oneTimeToken;
|
||||
|
@ -54,6 +57,6 @@ public class OneTimeTokenServiceImpl implements OneTimeTokenService {
|
|||
Assert.hasText(oneTimeToken, "One-time token must not be blank");
|
||||
|
||||
// Delete the token
|
||||
cacheStore.delete(oneTimeToken);
|
||||
cacheStore.delete(tokenPrefix + oneTimeToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package run.halo.app.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
@ -44,6 +46,16 @@ public interface BackupService {
|
|||
@NonNull
|
||||
List<BackupDTO> listWorkDirBackups();
|
||||
|
||||
/**
|
||||
* Get backup data by backup file name.
|
||||
*
|
||||
* @param backupFileName backup file name must not be blank
|
||||
* @param type backup type must not be null
|
||||
* @return an optional of backup data
|
||||
*/
|
||||
@NonNull
|
||||
Optional<BackupDTO> getBackup(@NonNull Path backupFileName, @NonNull BackupType type);
|
||||
|
||||
/**
|
||||
* Deletes backup.
|
||||
*
|
||||
|
@ -116,4 +128,26 @@ public interface BackupService {
|
|||
* @param fileName file name
|
||||
*/
|
||||
void deleteMarkdown(@NonNull String fileName);
|
||||
|
||||
/**
|
||||
* Backup type.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
enum BackupType {
|
||||
WHOLE_SITE("/api/admin/backups/work-dir"),
|
||||
JSON_DATA("/api/admin/backups/data"),
|
||||
MARKDOWN("/api/admin/backups/markdown/export"),
|
||||
;
|
||||
|
||||
private final String baseUri;
|
||||
|
||||
BackupType(String baseUri) {
|
||||
this.baseUri = baseUri;
|
||||
}
|
||||
|
||||
public String getBaseUri() {
|
||||
return baseUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import static run.halo.app.model.support.HaloConst.HALO_BACKUP_MARKDOWN_PREFIX;
|
|||
import static run.halo.app.model.support.HaloConst.HALO_BACKUP_PREFIX;
|
||||
import static run.halo.app.model.support.HaloConst.HALO_DATA_EXPORT_PREFIX;
|
||||
import static run.halo.app.utils.DateTimeUtils.HORIZONTAL_LINE_DATETIME_FORMATTER;
|
||||
import static run.halo.app.utils.FileUtils.checkDirectoryTraversal;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
|
@ -12,10 +13,7 @@ import cn.hutool.core.util.CharsetUtil;
|
|||
import cn.hutool.core.util.IdUtil;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.MalformedURLException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
@ -119,16 +117,8 @@ public class BackupServiceImpl implements BackupService {
|
|||
|
||||
private static final String DATA_EXPORT_BASE_URI = "/api/admin/backups/data";
|
||||
|
||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
|
||||
private static final String UPLOAD_SUB_DIR = "upload/";
|
||||
|
||||
private static final Type MAP_TYPE = new TypeToken<Map<String, ?>>() {
|
||||
}.getType();
|
||||
|
||||
private static final Type JSON_OBJECT_TYPE = new TypeToken<List<JsonObject>>() {
|
||||
}.getType();
|
||||
|
||||
private final AttachmentService attachmentService;
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
@ -214,18 +204,6 @@ public class BackupServiceImpl implements BackupService {
|
|||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the specified file name.
|
||||
*
|
||||
* @param unSanitized the specified file name
|
||||
* @return sanitized file name
|
||||
*/
|
||||
public static String sanitizeFilename(final String unSanitized) {
|
||||
return unSanitized.replaceAll("[^(a-zA-Z0-9\\u4e00-\\u9fa5\\.)]", "")
|
||||
.replaceAll("[\\?\\\\/:|<>\\*\\[\\]\\(\\)\\$%\\{\\}@~\\.]", "")
|
||||
.replaceAll("\\s", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException {
|
||||
|
||||
|
@ -233,7 +211,6 @@ public class BackupServiceImpl implements BackupService {
|
|||
String markdown = IoUtil.read(file.getInputStream(), StandardCharsets.UTF_8);
|
||||
|
||||
// TODO sheet import
|
||||
|
||||
return postService.importMarkdown(markdown, file.getOriginalFilename());
|
||||
}
|
||||
|
||||
|
@ -285,6 +262,16 @@ public class BackupServiceImpl implements BackupService {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BackupDTO> getBackup(@NonNull Path backupFilePath, @NonNull BackupType type) {
|
||||
if (Files.notExists(backupFilePath)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
BackupDTO backupDto = buildBackupDto(type.getBaseUri(), backupFilePath);
|
||||
return Optional.of(backupDto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteWorkDirBackup(String fileName) {
|
||||
Assert.hasText(fileName, "File name must not be blank");
|
||||
|
@ -295,7 +282,7 @@ public class BackupServiceImpl implements BackupService {
|
|||
Path backupPath = backupRootPath.resolve(fileName);
|
||||
|
||||
// Check directory traversal
|
||||
run.halo.app.utils.FileUtils.checkDirectoryTraversal(backupRootPath, backupPath);
|
||||
checkDirectoryTraversal(backupRootPath, backupPath);
|
||||
|
||||
try {
|
||||
// Delete backup file
|
||||
|
@ -324,7 +311,7 @@ public class BackupServiceImpl implements BackupService {
|
|||
Path backupFilePath = Paths.get(basePath, fileName).normalize();
|
||||
|
||||
// Check directory traversal
|
||||
run.halo.app.utils.FileUtils.checkDirectoryTraversal(backupParentPath, backupFilePath);
|
||||
checkDirectoryTraversal(backupParentPath, backupFilePath);
|
||||
|
||||
// Build url resource
|
||||
Resource backupResource = new UrlResource(backupFilePath.toUri());
|
||||
|
@ -418,7 +405,7 @@ public class BackupServiceImpl implements BackupService {
|
|||
|
||||
Path backupPath = dataExportRootPath.resolve(fileName);
|
||||
|
||||
run.halo.app.utils.FileUtils.checkDirectoryTraversal(dataExportRootPath, backupPath);
|
||||
checkDirectoryTraversal(dataExportRootPath, backupPath);
|
||||
|
||||
try {
|
||||
// Delete backup file
|
||||
|
@ -436,7 +423,7 @@ public class BackupServiceImpl implements BackupService {
|
|||
|
||||
ObjectMapper mapper = JsonUtils.createDefaultJsonMapper();
|
||||
TypeReference<HashMap<String, Object>> typeRef =
|
||||
new TypeReference<HashMap<String, Object>>() {
|
||||
new TypeReference<>() {
|
||||
};
|
||||
HashMap<String, Object> data = mapper.readValue(jsonContent, typeRef);
|
||||
|
||||
|
@ -539,19 +526,18 @@ public class BackupServiceImpl implements BackupService {
|
|||
// Write files to the temporary directory
|
||||
String markdownFileTempPathName =
|
||||
haloProperties.getBackupMarkdownDir() + IdUtil.simpleUUID().hashCode();
|
||||
for (int i = 0; i < postMarkdownList.size(); i++) {
|
||||
PostMarkdownVO postMarkdownVO = postMarkdownList.get(i);
|
||||
for (PostMarkdownVO postMarkdownVo : postMarkdownList) {
|
||||
StringBuilder content = new StringBuilder();
|
||||
Boolean needFrontMatter =
|
||||
Optional.ofNullable(postMarkdownParam.getNeedFrontMatter()).orElse(false);
|
||||
if (needFrontMatter) {
|
||||
// Add front-matter
|
||||
content.append(postMarkdownVO.getFrontMatter()).append("\n");
|
||||
content.append(postMarkdownVo.getFrontMatter()).append("\n");
|
||||
}
|
||||
content.append(postMarkdownVO.getOriginalContent());
|
||||
content.append(postMarkdownVo.getOriginalContent());
|
||||
try {
|
||||
String markdownFileName =
|
||||
postMarkdownVO.getTitle() + "-" + postMarkdownVO.getSlug() + ".md";
|
||||
postMarkdownVo.getTitle() + "-" + postMarkdownVo.getSlug() + ".md";
|
||||
Path markdownFilePath = Paths.get(markdownFileTempPathName, markdownFileName);
|
||||
if (!Files.exists(markdownFilePath.getParent())) {
|
||||
Files.createDirectories(markdownFilePath.getParent());
|
||||
|
@ -565,23 +551,21 @@ public class BackupServiceImpl implements BackupService {
|
|||
}
|
||||
}
|
||||
|
||||
ZipOutputStream markdownZipOut = null;
|
||||
// Create zip path
|
||||
String markdownZipFileName = HALO_BACKUP_MARKDOWN_PREFIX
|
||||
+ DateTimeUtils.format(LocalDateTime.now(), HORIZONTAL_LINE_DATETIME_FORMATTER)
|
||||
+ IdUtil.simpleUUID().hashCode() + ".zip";
|
||||
|
||||
// Create zip file
|
||||
Path markdownZipFilePath =
|
||||
Paths.get(haloProperties.getBackupMarkdownDir(), markdownZipFileName);
|
||||
if (!Files.exists(markdownZipFilePath.getParent())) {
|
||||
Files.createDirectories(markdownZipFilePath.getParent());
|
||||
}
|
||||
Path markdownZipPath = Files.createFile(markdownZipFilePath);
|
||||
// Zip file
|
||||
try {
|
||||
// Create zip path
|
||||
String markdownZipFileName = HALO_BACKUP_MARKDOWN_PREFIX
|
||||
+ DateTimeUtils.format(LocalDateTime.now(), HORIZONTAL_LINE_DATETIME_FORMATTER)
|
||||
+ IdUtil.simpleUUID().hashCode() + ".zip";
|
||||
|
||||
// Create zip file
|
||||
Path markdownZipFilePath =
|
||||
Paths.get(haloProperties.getBackupMarkdownDir(), markdownZipFileName);
|
||||
if (!Files.exists(markdownZipFilePath.getParent())) {
|
||||
Files.createDirectories(markdownZipFilePath.getParent());
|
||||
}
|
||||
Path markdownZipPath = Files.createFile(markdownZipFilePath);
|
||||
|
||||
markdownZipOut = new ZipOutputStream(Files.newOutputStream(markdownZipPath));
|
||||
try (ZipOutputStream markdownZipOut = new ZipOutputStream(
|
||||
Files.newOutputStream(markdownZipPath))) {
|
||||
|
||||
// Zip temporary directory
|
||||
Path markdownFileTempPath = Paths.get(markdownFileTempPathName);
|
||||
|
@ -602,10 +586,6 @@ public class BackupServiceImpl implements BackupService {
|
|||
return buildBackupDto(DATA_EXPORT_MARKDOWN_BASE_URI, markdownZipPath);
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("Failed to export markdowns", e);
|
||||
} finally {
|
||||
if (markdownZipOut != null) {
|
||||
markdownZipOut.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -632,22 +612,22 @@ public class BackupServiceImpl implements BackupService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void deleteMarkdown(String fileName) {
|
||||
Assert.hasText(fileName, "File name must not be blank");
|
||||
public void deleteMarkdown(String filename) {
|
||||
Assert.hasText(filename, "File name must not be blank");
|
||||
|
||||
Path backupRootPath = Paths.get(haloProperties.getBackupMarkdownDir());
|
||||
|
||||
// Get backup path
|
||||
Path backupPath = backupRootPath.resolve(fileName);
|
||||
Path backupPath = backupRootPath.resolve(filename);
|
||||
|
||||
// Check directory traversal
|
||||
run.halo.app.utils.FileUtils.checkDirectoryTraversal(backupRootPath, backupPath);
|
||||
checkDirectoryTraversal(backupRootPath, backupPath);
|
||||
|
||||
try {
|
||||
// Delete backup file
|
||||
Files.delete(backupPath);
|
||||
} catch (NoSuchFileException e) {
|
||||
throw new NotFoundException("The file " + fileName + " was not found", e);
|
||||
throw new NotFoundException("The file " + filename + " was not found", e);
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("Failed to delete backup", e);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue