refactor(directories): improve error handling
This commit is contained in:
@@ -1,12 +1,7 @@
|
|||||||
package com.ddf.vodsystem.controllers;
|
package com.ddf.vodsystem.controllers;
|
||||||
|
|
||||||
import com.ddf.vodsystem.dto.APIResponse;
|
import com.ddf.vodsystem.dto.APIResponse;
|
||||||
import com.ddf.vodsystem.exceptions.AlreadyStreaming;
|
import com.ddf.vodsystem.exceptions.*;
|
||||||
import com.ddf.vodsystem.exceptions.FFMPEGException;
|
|
||||||
import com.ddf.vodsystem.exceptions.JobNotFinished;
|
|
||||||
import com.ddf.vodsystem.exceptions.JobNotFound;
|
|
||||||
import com.ddf.vodsystem.exceptions.KeyNotFound;
|
|
||||||
import com.ddf.vodsystem.exceptions.NotAuthenticated;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||||
@@ -92,4 +87,11 @@ public class GlobalExceptionHandler {
|
|||||||
APIResponse<Void> response = new APIResponse<>(ERROR, ex.getMessage(), null);
|
APIResponse<Void> response = new APIResponse<>(ERROR, ex.getMessage(), null);
|
||||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
|
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(StorageException.class)
|
||||||
|
public ResponseEntity<APIResponse<Void>> handleStorage(StorageException e) {
|
||||||
|
logger.error("Storage operation failed", e);
|
||||||
|
APIResponse<Void> response = new APIResponse<>(ERROR, "Could not process file", null);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ public class JobStatus {
|
|||||||
private ProgressTracker process = new ProgressTracker();
|
private ProgressTracker process = new ProgressTracker();
|
||||||
private ProgressTracker conversion = new ProgressTracker();
|
private ProgressTracker conversion = new ProgressTracker();
|
||||||
private boolean failed = false;
|
private boolean failed = false;
|
||||||
|
private String failedReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.ddf.vodsystem.exceptions;
|
||||||
|
|
||||||
|
public class StorageException extends RuntimeException {
|
||||||
|
public StorageException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
public StorageException(String message, Throwable cause) { super(message, cause); }
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import java.util.concurrent.ExecutionException;
|
|||||||
|
|
||||||
import com.ddf.vodsystem.exceptions.FFMPEGException;
|
import com.ddf.vodsystem.exceptions.FFMPEGException;
|
||||||
import com.ddf.vodsystem.exceptions.NotAuthenticated;
|
import com.ddf.vodsystem.exceptions.NotAuthenticated;
|
||||||
|
import com.ddf.vodsystem.exceptions.StorageException;
|
||||||
import com.ddf.vodsystem.repositories.ClipRepository;
|
import com.ddf.vodsystem.repositories.ClipRepository;
|
||||||
import com.ddf.vodsystem.services.media.MetadataService;
|
import com.ddf.vodsystem.services.media.MetadataService;
|
||||||
import com.ddf.vodsystem.services.media.ThumbnailService;
|
import com.ddf.vodsystem.services.media.ThumbnailService;
|
||||||
@@ -152,10 +153,17 @@ public class ClipService {
|
|||||||
User user,
|
User user,
|
||||||
File tempFile,
|
File tempFile,
|
||||||
String fileName) {
|
String fileName) {
|
||||||
// Move clip from temp to output directory
|
File clipFile;
|
||||||
File clipFile = directoryService.getUserClipsFile(user.getId(), fileName);
|
File thumbnailFile;
|
||||||
File thumbnailFile = directoryService.getUserThumbnailsFile(user.getId(), fileName + ".png");
|
|
||||||
|
// Move temp file from temp dir to output dir
|
||||||
|
try {
|
||||||
|
clipFile = directoryService.getUserClipsFile(user.getId(), fileName);
|
||||||
|
thumbnailFile = directoryService.getUserThumbnailsFile(user.getId(), fileName + ".png");
|
||||||
directoryService.cutFile(tempFile, clipFile);
|
directoryService.cutFile(tempFile, clipFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new StorageException("Failed to move clip from temporary directory to output directory", e);
|
||||||
|
}
|
||||||
|
|
||||||
ClipOptions clipMetadata;
|
ClipOptions clipMetadata;
|
||||||
try {
|
try {
|
||||||
@@ -165,11 +173,14 @@ public class ClipService {
|
|||||||
throw new FFMPEGException("Error retrieving video metadata for clip: " + e.getMessage());
|
throw new FFMPEGException("Error retrieving video metadata for clip: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Thumbnail generation can fail with error propagation
|
||||||
try {
|
try {
|
||||||
thumbnailService.createThumbnail(clipFile, thumbnailFile, 0.0f);
|
thumbnailService.createThumbnail(clipFile, thumbnailFile, 0.0f);
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
logger.error("Error generating thumbnail for user: {}, {}", user.getId(), e.getMessage());
|
logger.error("Thumbnail generation interrupted for user: {}", user.getId(), e);
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error generating thumbnail for user: {}", user.getId(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
clipMetadata.setTitle(clipOptions.getTitle());
|
clipMetadata.setTitle(clipOptions.getTitle());
|
||||||
|
|||||||
@@ -42,80 +42,72 @@ public class DirectoryService {
|
|||||||
return new File(dir);
|
return new File(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getUserClipsFile(Long userId, String fileName) {
|
public File getUserFolder(Long userId) throws IOException {
|
||||||
if (userId == null || fileName == null || fileName.isEmpty()) {
|
if (userId == null) {
|
||||||
throw new IllegalArgumentException("User ID and file name cannot be null or empty");
|
throw new IllegalArgumentException("User ID cannot be null or empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
String dir = outputDir + File.separator + userId + File.separator + "clips" + File.separator + fileName;
|
String dir = outputDir + File.separator + userId;
|
||||||
File file = new File(dir);
|
createDirectory(dir);
|
||||||
|
return new File(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getUserClipsFile(Long userId, String filename) throws IOException {
|
||||||
|
if (filename == null || filename.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("File name cannot be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
File userDir = getUserFolder(userId);
|
||||||
|
String fullPath = userDir.getAbsolutePath() + File.separator + "clips" + File.separator + filename;
|
||||||
|
File file = new File(fullPath);
|
||||||
|
|
||||||
try {
|
|
||||||
createDirectory(file.getParent());
|
createDirectory(file.getParent());
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error creating clips directory: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getVodFile(String filename) throws IOException {
|
public File getVodFile(Long userId, String filename) throws IOException {
|
||||||
String dir = outputDir + File.separator + "vods" + File.separator + filename;
|
if (filename == null || filename.isEmpty()) {
|
||||||
File file = new File(dir);
|
throw new IllegalArgumentException("File name cannot be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
String userDir = getUserFolder(userId).getAbsolutePath();
|
||||||
|
File file = new File(userDir + File.separator + "vods" + File.separator + filename);
|
||||||
createDirectory(file.getParent());
|
createDirectory(file.getParent());
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getUserThumbnailsFile(Long userId, String fileName) {
|
public File getUserThumbnailsFile(Long userId, String filename) throws IOException {
|
||||||
if (userId == null || fileName == null || fileName.isEmpty()) {
|
if (filename == null || filename.isEmpty()) {
|
||||||
throw new IllegalArgumentException("User ID and file name cannot be null or empty");
|
throw new IllegalArgumentException("File name cannot be null or empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
String dir = outputDir + File.separator + userId + File.separator + "thumbnails" + File.separator + fileName;
|
String userDir = getUserFolder(userId).getAbsolutePath();
|
||||||
File file = new File(dir);
|
File file = new File(userDir + File.separator + "thumbnails" + File.separator + filename);
|
||||||
|
|
||||||
try {
|
|
||||||
createDirectory(file.getParent());
|
createDirectory(file.getParent());
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error creating thumbnails directory: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveAtDir(File file, MultipartFile multipartFile) {
|
public void saveMultipartFile(File file, MultipartFile multipartFile) throws IOException {
|
||||||
try {
|
createDirectory(file.getParent());
|
||||||
createDirectory(file.getAbsolutePath());
|
|
||||||
Path filePath = Paths.get(file.getAbsolutePath());
|
Path filePath = Paths.get(file.getAbsolutePath());
|
||||||
Files.copy(multipartFile.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(multipartFile.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyFile(File source, File target) {
|
public void copyFile(File source, File target) throws IOException {
|
||||||
Path sourcePath = Paths.get(source.getAbsolutePath());
|
Path sourcePath = Paths.get(source.getAbsolutePath());
|
||||||
Path destPath = Paths.get(target.getAbsolutePath());
|
Path destPath = Paths.get(target.getAbsolutePath());
|
||||||
|
|
||||||
try {
|
|
||||||
Files.createDirectories(destPath.getParent());
|
Files.createDirectories(destPath.getParent());
|
||||||
Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
logger.info("Copied file from {} to {}", sourcePath, destPath);
|
logger.info("Copied file from {} to {}", sourcePath, destPath);
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cutFile(File source, File target) {
|
public void cutFile(File source, File target) throws IOException {
|
||||||
copyFile(source, target);
|
copyFile(source, target);
|
||||||
|
|
||||||
try {
|
|
||||||
Files.deleteIfExists(source.toPath());
|
Files.deleteIfExists(source.toPath());
|
||||||
logger.info("Deleted source file: {}", source.getAbsolutePath());
|
logger.info("Deleted source file: {}", source.getAbsolutePath());
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error deleting source file: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFileExtension(String filePath) {
|
public String getFileExtension(String filePath) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
import com.ddf.vodsystem.dto.Job;
|
import com.ddf.vodsystem.dto.Job;
|
||||||
import com.ddf.vodsystem.entities.User;
|
import com.ddf.vodsystem.entities.User;
|
||||||
|
import com.ddf.vodsystem.exceptions.StorageException;
|
||||||
import com.ddf.vodsystem.services.media.CompressionService;
|
import com.ddf.vodsystem.services.media.CompressionService;
|
||||||
import com.ddf.vodsystem.services.media.RemuxService;
|
import com.ddf.vodsystem.services.media.RemuxService;
|
||||||
|
|
||||||
@@ -76,7 +77,12 @@ public class JobService {
|
|||||||
public void convertJob(Job job) {
|
public void convertJob(Job job) {
|
||||||
logger.info("Converting job: {}", job.getUuid());
|
logger.info("Converting job: {}", job.getUuid());
|
||||||
File tempFile = new File(job.getInputFile().getAbsolutePath() + ".temp");
|
File tempFile = new File(job.getInputFile().getAbsolutePath() + ".temp");
|
||||||
|
|
||||||
|
try {
|
||||||
directoryService.copyFile(job.getInputFile(), tempFile);
|
directoryService.copyFile(job.getInputFile(), tempFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new StorageException("Could not create temporary file: " + tempFile, e);
|
||||||
|
}
|
||||||
|
|
||||||
job.getStatus().getConversion().reset();
|
job.getStatus().getConversion().reset();
|
||||||
|
|
||||||
@@ -92,7 +98,7 @@ public class JobService {
|
|||||||
try {
|
try {
|
||||||
Files.deleteIfExists(Paths.get(tempFile.getAbsolutePath()));
|
Files.deleteIfExists(Paths.get(tempFile.getAbsolutePath()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new StorageException("Could not delete path at: " + tempFile.getAbsolutePath(), e);
|
||||||
}
|
}
|
||||||
}).whenComplete((ignored, throwable) -> {
|
}).whenComplete((ignored, throwable) -> {
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
@@ -129,11 +135,14 @@ public class JobService {
|
|||||||
)).exceptionally(
|
)).exceptionally(
|
||||||
ex -> {
|
ex -> {
|
||||||
job.getStatus().setFailed(true);
|
job.getStatus().setFailed(true);
|
||||||
|
job.getStatus().setFailedReason(ex.getMessage());
|
||||||
|
logger.error("Error processing job {}: {}", job.getUuid(), ex.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
job.getStatus().setFailed(true);
|
job.getStatus().setFailed(true);
|
||||||
|
job.getStatus().setFailedReason(e.getMessage());
|
||||||
logger.error("Error processing job {}: {}", job.getUuid(), e.getMessage());
|
logger.error("Error processing job {}: {}", job.getUuid(), e.getMessage());
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ public class StreamService {
|
|||||||
float trimOffset = Math.max(0f, (startTime.toEpochMilli() - firstSegmentMs) / 1000f);
|
float trimOffset = Math.max(0f, (startTime.toEpochMilli() - firstSegmentMs) / 1000f);
|
||||||
float duration = (endTime.toEpochMilli() - startTime.toEpochMilli()) / 1000f;
|
float duration = (endTime.toEpochMilli() - startTime.toEpochMilli()) / 1000f;
|
||||||
|
|
||||||
File outputFile = directoryService.getVodFile(UUID.randomUUID() + ".mp4");
|
File outputFile = directoryService.getVodFile(user.getId(), UUID.randomUUID() + ".mp4");
|
||||||
streamActionsService.saveSection(fileSegments, trimOffset, duration, outputFile);
|
streamActionsService.saveSection(fileSegments, trimOffset, duration, outputFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ package com.ddf.vodsystem.services;
|
|||||||
import com.ddf.vodsystem.dto.Job;
|
import com.ddf.vodsystem.dto.Job;
|
||||||
import com.ddf.vodsystem.dto.ClipOptions;
|
import com.ddf.vodsystem.dto.ClipOptions;
|
||||||
import com.ddf.vodsystem.exceptions.FFMPEGException;
|
import com.ddf.vodsystem.exceptions.FFMPEGException;
|
||||||
|
import com.ddf.vodsystem.exceptions.StorageException;
|
||||||
import com.ddf.vodsystem.services.media.MetadataService;
|
import com.ddf.vodsystem.services.media.MetadataService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -41,10 +43,14 @@ public class UploadService {
|
|||||||
|
|
||||||
File inputFile = directoryService.getTempInputFile(uuid + "." + extension);
|
File inputFile = directoryService.getTempInputFile(uuid + "." + extension);
|
||||||
File outputFile = directoryService.getTempOutputFile(uuid + ".mp4");
|
File outputFile = directoryService.getTempOutputFile(uuid + ".mp4");
|
||||||
directoryService.saveAtDir(inputFile, file);
|
|
||||||
|
|
||||||
// add job
|
try {
|
||||||
logger.info("Uploaded file and creating job with UUID: {}", uuid);
|
directoryService.saveMultipartFile(inputFile, file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new StorageException("Could not save Multipart file at: " + inputFile, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Multi-part file uploaded at {}", inputFile);
|
||||||
|
|
||||||
ClipOptions clipOptions = getMetadataWithTimeout(inputFile);
|
ClipOptions clipOptions = getMetadataWithTimeout(inputFile);
|
||||||
Job job = new Job(uuid, inputFile, outputFile, clipOptions);
|
Job job = new Job(uuid, inputFile, outputFile, clipOptions);
|
||||||
|
|||||||
Reference in New Issue
Block a user