refactor(directories): improve error handling

This commit is contained in:
2026-06-20 12:38:40 +01:00
parent 94269459b9
commit 1062f72661
8 changed files with 94 additions and 65 deletions

View File

@@ -1,12 +1,7 @@
package com.ddf.vodsystem.controllers;
import com.ddf.vodsystem.dto.APIResponse;
import com.ddf.vodsystem.exceptions.AlreadyStreaming;
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 com.ddf.vodsystem.exceptions.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotSupportedException;
@@ -92,4 +87,11 @@ public class GlobalExceptionHandler {
APIResponse<Void> response = new APIResponse<>(ERROR, ex.getMessage(), null);
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);
}
}

View File

@@ -7,6 +7,7 @@ public class JobStatus {
private ProgressTracker process = new ProgressTracker();
private ProgressTracker conversion = new ProgressTracker();
private boolean failed = false;
private String failedReason;
}

View File

@@ -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); }
}

View File

@@ -13,6 +13,7 @@ import java.util.concurrent.ExecutionException;
import com.ddf.vodsystem.exceptions.FFMPEGException;
import com.ddf.vodsystem.exceptions.NotAuthenticated;
import com.ddf.vodsystem.exceptions.StorageException;
import com.ddf.vodsystem.repositories.ClipRepository;
import com.ddf.vodsystem.services.media.MetadataService;
import com.ddf.vodsystem.services.media.ThumbnailService;
@@ -152,10 +153,17 @@ public class ClipService {
User user,
File tempFile,
String fileName) {
// Move clip from temp to output directory
File clipFile = directoryService.getUserClipsFile(user.getId(), fileName);
File thumbnailFile = directoryService.getUserThumbnailsFile(user.getId(), fileName + ".png");
File clipFile;
File thumbnailFile;
// 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);
} catch (IOException e) {
throw new StorageException("Failed to move clip from temporary directory to output directory", e);
}
ClipOptions clipMetadata;
try {
@@ -165,11 +173,14 @@ public class ClipService {
throw new FFMPEGException("Error retrieving video metadata for clip: " + e.getMessage());
}
// Thumbnail generation can fail with error propagation
try {
thumbnailService.createThumbnail(clipFile, thumbnailFile, 0.0f);
} catch (IOException | InterruptedException e) {
logger.error("Error generating thumbnail for user: {}, {}", user.getId(), e.getMessage());
} catch (InterruptedException e) {
logger.error("Thumbnail generation interrupted for user: {}", user.getId(), e);
Thread.currentThread().interrupt();
} catch (IOException e) {
logger.error("Error generating thumbnail for user: {}", user.getId(), e);
}
clipMetadata.setTitle(clipOptions.getTitle());

View File

@@ -42,80 +42,72 @@ public class DirectoryService {
return new File(dir);
}
public File getUserClipsFile(Long userId, String fileName) {
if (userId == null || fileName == null || fileName.isEmpty()) {
throw new IllegalArgumentException("User ID and file name cannot be null or empty");
public File getUserFolder(Long userId) throws IOException {
if (userId == null) {
throw new IllegalArgumentException("User ID cannot be null or empty");
}
String dir = outputDir + File.separator + userId + File.separator + "clips" + File.separator + fileName;
File file = new File(dir);
String dir = outputDir + File.separator + userId;
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());
} catch (IOException e) {
logger.error("Error creating clips directory: {}", e.getMessage());
}
return file;
}
public File getVodFile(String filename) throws IOException {
String dir = outputDir + File.separator + "vods" + File.separator + filename;
File file = new File(dir);
public File getVodFile(Long userId, String filename) throws IOException {
if (filename == null || filename.isEmpty()) {
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());
return file;
}
public File getUserThumbnailsFile(Long userId, String fileName) {
if (userId == null || fileName == null || fileName.isEmpty()) {
throw new IllegalArgumentException("User ID and file name cannot be null or empty");
public File getUserThumbnailsFile(Long userId, String filename) throws IOException {
if (filename == null || filename.isEmpty()) {
throw new IllegalArgumentException("File name cannot be null or empty");
}
String dir = outputDir + File.separator + userId + File.separator + "thumbnails" + File.separator + fileName;
File file = new File(dir);
try {
String userDir = getUserFolder(userId).getAbsolutePath();
File file = new File(userDir + File.separator + "thumbnails" + File.separator + filename);
createDirectory(file.getParent());
} catch (IOException e) {
logger.error("Error creating thumbnails directory: {}", e.getMessage());
}
return file;
}
public void saveAtDir(File file, MultipartFile multipartFile) {
try {
createDirectory(file.getAbsolutePath());
public void saveMultipartFile(File file, MultipartFile multipartFile) throws IOException {
createDirectory(file.getParent());
Path filePath = Paths.get(file.getAbsolutePath());
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 destPath = Paths.get(target.getAbsolutePath());
try {
Files.createDirectories(destPath.getParent());
Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING);
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);
try {
Files.deleteIfExists(source.toPath());
logger.info("Deleted source file: {}", source.getAbsolutePath());
} catch (IOException e) {
logger.error("Error deleting source file: {}", e.getMessage());
}
}
public String getFileExtension(String filePath) {

View File

@@ -9,6 +9,7 @@ import java.util.concurrent.ConcurrentHashMap;
import com.ddf.vodsystem.dto.Job;
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.RemuxService;
@@ -76,7 +77,12 @@ public class JobService {
public void convertJob(Job job) {
logger.info("Converting job: {}", job.getUuid());
File tempFile = new File(job.getInputFile().getAbsolutePath() + ".temp");
try {
directoryService.copyFile(job.getInputFile(), tempFile);
} catch (IOException e) {
throw new StorageException("Could not create temporary file: " + tempFile, e);
}
job.getStatus().getConversion().reset();
@@ -92,7 +98,7 @@ public class JobService {
try {
Files.deleteIfExists(Paths.get(tempFile.getAbsolutePath()));
} catch (IOException e) {
throw new RuntimeException(e);
throw new StorageException("Could not delete path at: " + tempFile.getAbsolutePath(), e);
}
}).whenComplete((ignored, throwable) -> {
if (throwable != null) {
@@ -129,11 +135,14 @@ public class JobService {
)).exceptionally(
ex -> {
job.getStatus().setFailed(true);
job.getStatus().setFailedReason(ex.getMessage());
logger.error("Error processing job {}: {}", job.getUuid(), ex.getMessage());
return null;
}
);
} catch (IOException | InterruptedException e) {
job.getStatus().setFailed(true);
job.getStatus().setFailedReason(e.getMessage());
logger.error("Error processing job {}: {}", job.getUuid(), e.getMessage());
Thread.currentThread().interrupt();
}

View File

@@ -141,7 +141,7 @@ public class StreamService {
float trimOffset = Math.max(0f, (startTime.toEpochMilli() - firstSegmentMs) / 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);
}

View File

@@ -3,11 +3,13 @@ package com.ddf.vodsystem.services;
import com.ddf.vodsystem.dto.Job;
import com.ddf.vodsystem.dto.ClipOptions;
import com.ddf.vodsystem.exceptions.FFMPEGException;
import com.ddf.vodsystem.exceptions.StorageException;
import com.ddf.vodsystem.services.media.MetadataService;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.UUID;
@@ -41,10 +43,14 @@ public class UploadService {
File inputFile = directoryService.getTempInputFile(uuid + "." + extension);
File outputFile = directoryService.getTempOutputFile(uuid + ".mp4");
directoryService.saveAtDir(inputFile, file);
// add job
logger.info("Uploaded file and creating job with UUID: {}", uuid);
try {
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);
Job job = new Job(uuid, inputFile, outputFile, clipOptions);