From 9a36510dc4958563223736ba0d27ab8870da2530 Mon Sep 17 00:00:00 2001 From: DennisTurco Date: Tue, 17 Feb 2026 16:30:34 +0100 Subject: [PATCH 1/2] avoid spam automatic emails --- .../java/backupmanager/BackupOperations.java | 22 ++++---- .../Dialogs/BackupEntryDialog.java | 4 +- .../java/backupmanager/Email/EmailSender.java | 52 +++++++++++++++---- .../backupmanager/Entities/BackupRequest.java | 12 ++--- .../java/backupmanager/Entities/Email.java | 7 ++- ...ackupStatusEnum.java => BackupStatus.java} | 8 +-- ...ggeredEnum.java => BackupTriggerType.java} | 8 +-- .../Enums/{ErrorTypes.java => ErrorType.java} | 2 +- .../Controllers/BackupPopupController.java | 4 +- .../Services/BackgroundService.java | 6 +-- .../Services/RunningBackupService.java | 8 +-- .../backupmanager/Services/ZippingThread.java | 8 +-- .../Repositories/BackupRequestRepository.java | 22 ++++---- .../Repositories/EmailRepository.java | 8 +-- src/main/resources/db/002_seed.sql | 2 +- src/main/resources/res/config/config.json | 13 +++-- .../BackupRequestRepositoryTest.java | 10 ++-- .../repositories/EmailRepositoryTest.java | 2 +- 18 files changed, 120 insertions(+), 78 deletions(-) rename src/main/java/backupmanager/Enums/{BackupStatusEnum.java => BackupStatus.java} (61%) rename src/main/java/backupmanager/Enums/{BackupTriggeredEnum.java => BackupTriggerType.java} (57%) rename src/main/java/backupmanager/Enums/{ErrorTypes.java => ErrorType.java} (87%) diff --git a/src/main/java/backupmanager/BackupOperations.java b/src/main/java/backupmanager/BackupOperations.java index cfa1c191..aaa31ef8 100644 --- a/src/main/java/backupmanager/BackupOperations.java +++ b/src/main/java/backupmanager/BackupOperations.java @@ -21,9 +21,9 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; import backupmanager.Entities.ZippingContext; -import backupmanager.Enums.BackupStatusEnum; -import backupmanager.Enums.BackupTriggeredEnum; -import backupmanager.Enums.ErrorTypes; +import backupmanager.Enums.BackupStatus; +import backupmanager.Enums.BackupTriggerType; +import backupmanager.Enums.ErrorType; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; import backupmanager.GUI.BackupManagerGUI; @@ -39,7 +39,7 @@ public class BackupOperations { private static final Logger logger = LoggerFactory.getLogger(BackupOperations.class); - public static void singleBackup(ZippingContext context, BackupTriggeredEnum triggeredBy) { + public static void singleBackup(ZippingContext context, BackupTriggerType triggeredBy) { if (context.backup() == null) throw new IllegalArgumentException("Backup cannot be null!"); logger.info("Event --> manual backup started"); @@ -70,7 +70,7 @@ public static void singleBackup(ZippingContext context, BackupTriggeredEnum trig } } - public static void executeBackup(ZippingContext context, BackupTriggeredEnum triggeredBy, String path1, String path2) { + public static void executeBackup(ZippingContext context, BackupTriggerType triggeredBy, String path1, String path2) { File sourceFile = new File(path1.trim()); File outputFile = new File((path2+".zip").trim()); @@ -81,7 +81,7 @@ public static void executeBackup(ZippingContext context, BackupTriggeredEnum tri ZippingThread.zipDirectory(sourceFile, outputFile, context, totalFilesCount); } - private static void createBackupRequest(ZippingContext context, BackupTriggeredEnum triggeredBy, File sourceFile, File outputFile, int totalFilesCount) { + private static void createBackupRequest(ZippingContext context, BackupTriggerType triggeredBy, File sourceFile, File outputFile, int totalFilesCount) { long targetSize = FolderUtils.calculateFileOrFolderSize(sourceFile.getAbsolutePath()); BackupRequestRepository.insertBackupRequest(BackupRequest.createNewBackupRequest(context.backup().getId(), triggeredBy, outputFile.getAbsolutePath(), targetSize, totalFilesCount)); } @@ -162,17 +162,17 @@ else if (selectedFile.isFile()) public static boolean checkInputCorrect(String backupName, String path1, String path2, TrayIcon trayIcon) { //check if inputs are null if(path1.length() == 0 || path2.length() == 0) { - setError(ErrorTypes.InputMissing, trayIcon, backupName); + setError(ErrorType.InputMissing, trayIcon, backupName); return false; } if (!Files.exists(Path.of(path1)) || !Files.exists(Path.of(path2))) { - setError(ErrorTypes.InputError, trayIcon, backupName); + setError(ErrorType.InputError, trayIcon, backupName); return false; } if (path1.equals(path2)) { - setError(ErrorTypes.SamePaths, trayIcon, backupName); + setError(ErrorType.SamePaths, trayIcon, backupName); return false; } @@ -283,7 +283,7 @@ public static void deletePotentiallyIncompletedBackupsFromLastExecution() { for (BackupRequest request : requests) { boolean deleted = deletePartialBackup(request.outputPath()); if (deleted) { - BackupRequestRepository.updateRequestStatusByRequestId(request.backupRequestId(), BackupStatusEnum.TERMINATED); + BackupRequestRepository.updateRequestStatusByRequestId(request.backupRequestId(), BackupStatus.TERMINATED); } } } @@ -326,7 +326,7 @@ private static boolean deletePartialBackup(String filePath) { return false; } - public static void setError(ErrorTypes error, TrayIcon trayIcon, String backupName) { + public static void setError(ErrorType error, TrayIcon trayIcon, String backupName) { switch (error) { case InputMissing -> { logger.warn("Input Missing!"); diff --git a/src/main/java/backupmanager/Dialogs/BackupEntryDialog.java b/src/main/java/backupmanager/Dialogs/BackupEntryDialog.java index eb4b9c71..f53b678f 100644 --- a/src/main/java/backupmanager/Dialogs/BackupEntryDialog.java +++ b/src/main/java/backupmanager/Dialogs/BackupEntryDialog.java @@ -15,7 +15,7 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; import backupmanager.Entities.ZippingContext; -import backupmanager.Enums.BackupTriggeredEnum; +import backupmanager.Enums.BackupTriggerType; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; @@ -199,7 +199,7 @@ public void singleBackup(String path1, String path2, BackupTable backupTable) { ZippingContext context = ZippingContext.create(currentBackup, null, backupTable, BackupManagerGUI.progressBar, null, null); - BackupOperations.executeBackup(context, BackupTriggeredEnum.USER, path1, path2); + BackupOperations.executeBackup(context, BackupTriggerType.USER, path1, path2); //if current_file_opened is null it means they are not in a backup but it is a backup with no associated json file if (currentBackup.getName() != null && !currentBackup.getName().isEmpty()) { diff --git a/src/main/java/backupmanager/Email/EmailSender.java b/src/main/java/backupmanager/Email/EmailSender.java index 6503a032..9af56d66 100644 --- a/src/main/java/backupmanager/Email/EmailSender.java +++ b/src/main/java/backupmanager/Email/EmailSender.java @@ -5,16 +5,21 @@ import java.io.FileReader; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; import java.util.LinkedList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import backupmanager.Entities.Email; import backupmanager.Entities.User; import backupmanager.Enums.ConfigKey; +import backupmanager.Enums.EmailType; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Json.JSONConfigReader; +import backupmanager.database.Repositories.EmailRepository; import backupmanager.database.Repositories.UserRepository; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.net.SMTPAppender; @@ -24,6 +29,8 @@ */ public class EmailSender { + private static final JSONConfigReader configReader = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); + private static final Logger logger = LoggerFactory.getLogger(EmailSender.class); // Logger for sending critical error emails @@ -44,8 +51,13 @@ public static void sendErrorEmail(String subject, String body) { User user = getCurrentUser(); if (user == null) { - logger.warn("User is null, using a default user for the email"); - user = User.getDefaultUser(); + logger.warn("User is null. Cannot send critical error email"); + return; + } + + if (!canSend()) { + logger.info("A critical error occurred, but the email cannot be sent because insufficient time has passed since the last one."); + return; } int rows = 300; @@ -76,6 +88,8 @@ public static void sendErrorEmail(String subject, String body) { emailErrorLogger.error(emailMessage); // Log the message as ERROR, triggering the SMTPAppender logger.info("Error email sent with subject: " + subject); + + insertEmailInternally(EmailType.CRITICAL_ERROR, null); } /** @@ -90,6 +104,8 @@ public static void sendUserCreationEmail(User user) { emailInfoLogger.error(emailMessage); // Log the message as INFO, triggering the SMTPAppender logger.info("User creation info email sent with user: " + user.toString()); + + insertEmailInternally(EmailType.WELCOME, null); } /** @@ -101,7 +117,6 @@ public static void sendConfirmEmailToUser(User user) { String subject = TranslationCategory.USER_DIALOG.getTranslation(TranslationKey.EMAIL_CONFIRMATION_SUBJECT); String body = TranslationCategory.USER_DIALOG.getTranslation(TranslationKey.EMAIL_CONFIRMATION_BODY); - // Assicurati di assegnare il risultato della sostituzione body = body.replace("[UserName]", user.getUserCompleteName()); body = body.replace("[SupportEmail]", ConfigKey.EMAIL.getValue()); @@ -115,17 +130,23 @@ public static void sendConfirmEmailToUser(User user) { logger.info("Confirmation registration email sent to the user: " + user.toString()); } - private static User getCurrentUser() { - User user = UserRepository.getLastUser(); + private static void insertEmailInternally(EmailType type, String payload) { + Email email = Email.createNewEmail(type, ConfigKey.VERSION.getValue(), payload); + EmailRepository.insertEmail(email); + } - if (user == null) { - logger.error("Unable to retrieve user details for the email because there is no user registered"); - } + private static boolean canSend() { + int minWait = configReader.getConfigValue("CriticalEmailMinWaitDays", 7); + LocalDateTime now = LocalDateTime.now(); - return user; + Email email = EmailRepository.getLastEmailByType(EmailType.CRITICAL_ERROR); + if (email == null) return true; + LocalDateTime lastSent = email.insertDate(); + + return (lastSent.plusDays(minWait).isBefore(now)); } - public static String getTextFromLogFile(int rows) { + private static String getTextFromLogFile(int rows) { File file = new File(ConfigKey.LOG_DIRECTORY_STRING.getValue() + ConfigKey.LOG_FILE_STRING.getValue()); if (!file.exists() || !file.isFile() || file.length() == 0) { @@ -151,10 +172,19 @@ public static String getTextFromLogFile(int rows) { return String.join("\n", lastLines); } + private static User getCurrentUser() { + User user = UserRepository.getLastUser(); + + if (user == null) { + logger.error("Unable to retrieve user details for the email because there is no user registered"); + } + + return user; + } + private static void updateEmailRecipient(String newRecipient) { LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - //get the'appender SMTP SMTPAppender smtpAppender = (SMTPAppender) context.getLogger("EMAIL_CONFIRMATION_LOGGER").getAppender("EMAIL_CONFIRMATION_LOGGER"); // if exists -> update diff --git a/src/main/java/backupmanager/Entities/BackupRequest.java b/src/main/java/backupmanager/Entities/BackupRequest.java index debb5b6c..28dba56e 100644 --- a/src/main/java/backupmanager/Entities/BackupRequest.java +++ b/src/main/java/backupmanager/Entities/BackupRequest.java @@ -2,17 +2,17 @@ import java.time.LocalDateTime; -import backupmanager.Enums.BackupStatusEnum; -import backupmanager.Enums.BackupTriggeredEnum; +import backupmanager.Enums.BackupStatus; +import backupmanager.Enums.BackupTriggerType; public record BackupRequest ( int backupRequestId, int backupConfigurationId, LocalDateTime startedDate, LocalDateTime completionDate, - BackupStatusEnum status, + BackupStatus status, int progress, - BackupTriggeredEnum triggeredBy, + BackupTriggerType triggeredBy, Long durationMs, String outputPath, long unzippedTargetSize, @@ -21,7 +21,7 @@ public record BackupRequest ( String errorMessage ) { - public static BackupRequest createNewBackupRequest(int backupConfigurationId, BackupTriggeredEnum type, String outputPath, long targetSize, int filesCount) { - return new BackupRequest(0, backupConfigurationId, LocalDateTime.now(), null, BackupStatusEnum.IN_PROGRESS, 0, type, null, outputPath, targetSize, null, filesCount, null); + public static BackupRequest createNewBackupRequest(int backupConfigurationId, BackupTriggerType type, String outputPath, long targetSize, int filesCount) { + return new BackupRequest(0, backupConfigurationId, LocalDateTime.now(), null, BackupStatus.IN_PROGRESS, 0, type, null, outputPath, targetSize, null, filesCount, null); } } diff --git a/src/main/java/backupmanager/Entities/Email.java b/src/main/java/backupmanager/Entities/Email.java index 23babd4d..b4c28249 100644 --- a/src/main/java/backupmanager/Entities/Email.java +++ b/src/main/java/backupmanager/Entities/Email.java @@ -10,4 +10,9 @@ public record Email ( LocalDateTime insertDate, String appVersion, String payload -){ } +) +{ + public static Email createNewEmail(EmailType type, String appVersion, String payload) { + return new Email(0, type, LocalDateTime.now(), appVersion, payload); + } +} diff --git a/src/main/java/backupmanager/Enums/BackupStatusEnum.java b/src/main/java/backupmanager/Enums/BackupStatus.java similarity index 61% rename from src/main/java/backupmanager/Enums/BackupStatusEnum.java rename to src/main/java/backupmanager/Enums/BackupStatus.java index 754491a9..d7eabbe5 100644 --- a/src/main/java/backupmanager/Enums/BackupStatusEnum.java +++ b/src/main/java/backupmanager/Enums/BackupStatus.java @@ -3,7 +3,7 @@ import backupmanager.Enums.utils.CodeEnum; import backupmanager.Enums.utils.EnumUtil; -public enum BackupStatusEnum implements CodeEnum { +public enum BackupStatus implements CodeEnum { IN_PROGRESS(1), QUEUE(2), FINISHED(3), @@ -11,7 +11,7 @@ public enum BackupStatusEnum implements CodeEnum { private final int code; - BackupStatusEnum(int code) { + BackupStatus(int code) { this.code = code; } @@ -20,7 +20,7 @@ public int getCode() { return code; } - public static BackupStatusEnum fromCode(int code) { - return EnumUtil.fromCode(BackupStatusEnum.class, code); + public static BackupStatus fromCode(int code) { + return EnumUtil.fromCode(BackupStatus.class, code); } } diff --git a/src/main/java/backupmanager/Enums/BackupTriggeredEnum.java b/src/main/java/backupmanager/Enums/BackupTriggerType.java similarity index 57% rename from src/main/java/backupmanager/Enums/BackupTriggeredEnum.java rename to src/main/java/backupmanager/Enums/BackupTriggerType.java index 091813d9..82b65b2a 100644 --- a/src/main/java/backupmanager/Enums/BackupTriggeredEnum.java +++ b/src/main/java/backupmanager/Enums/BackupTriggerType.java @@ -3,13 +3,13 @@ import backupmanager.Enums.utils.CodeEnum; import backupmanager.Enums.utils.EnumUtil; -public enum BackupTriggeredEnum implements CodeEnum { +public enum BackupTriggerType implements CodeEnum { USER(1), SCHEDULER(2); private final int code; - BackupTriggeredEnum(int code) { + BackupTriggerType(int code) { this.code = code; } @@ -18,7 +18,7 @@ public int getCode() { return code; } - public static BackupTriggeredEnum fromCode(int code) { - return EnumUtil.fromCode(BackupTriggeredEnum.class, code); + public static BackupTriggerType fromCode(int code) { + return EnumUtil.fromCode(BackupTriggerType.class, code); } } diff --git a/src/main/java/backupmanager/Enums/ErrorTypes.java b/src/main/java/backupmanager/Enums/ErrorType.java similarity index 87% rename from src/main/java/backupmanager/Enums/ErrorTypes.java rename to src/main/java/backupmanager/Enums/ErrorType.java index 42be4fe4..48ad5218 100644 --- a/src/main/java/backupmanager/Enums/ErrorTypes.java +++ b/src/main/java/backupmanager/Enums/ErrorType.java @@ -1,6 +1,6 @@ package backupmanager.Enums; -public enum ErrorTypes { +public enum ErrorType { InputMissing, InputError, SamePaths, diff --git a/src/main/java/backupmanager/GUI/Controllers/BackupPopupController.java b/src/main/java/backupmanager/GUI/Controllers/BackupPopupController.java index ec7577ad..a42d9e6f 100644 --- a/src/main/java/backupmanager/GUI/Controllers/BackupPopupController.java +++ b/src/main/java/backupmanager/GUI/Controllers/BackupPopupController.java @@ -21,7 +21,7 @@ import backupmanager.BackupOperations; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.ZippingContext; -import backupmanager.Enums.BackupTriggeredEnum; +import backupmanager.Enums.BackupTriggerType; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; import backupmanager.GUI.BackupManagerGUI; @@ -110,7 +110,7 @@ public static void popupItemRunBackup(int selectedRow, BackupTable backupTable, BackupManagerGUI.progressBar = new BackupProgressGUI(backup.getTargetPath(), backup.getDestinationPath()); ZippingContext context = ZippingContext.create(backup, null, backupTable, BackupManagerGUI.progressBar, interruptBackupPopupItem, RunBackupPopupItem); - BackupOperations.singleBackup(context, BackupTriggeredEnum.USER); + BackupOperations.singleBackup(context, BackupTriggerType.USER); } } diff --git a/src/main/java/backupmanager/Services/BackgroundService.java b/src/main/java/backupmanager/Services/BackgroundService.java index 2fb9d782..94c8bdfe 100644 --- a/src/main/java/backupmanager/Services/BackgroundService.java +++ b/src/main/java/backupmanager/Services/BackgroundService.java @@ -18,7 +18,7 @@ import backupmanager.Entities.BackupRequest; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.ZippingContext; -import backupmanager.Enums.BackupTriggeredEnum; +import backupmanager.Enums.BackupTriggerType; import backupmanager.Enums.ConfigKey; import backupmanager.Json.JSONConfigReader; import backupmanager.database.Repositories.BackupConfigurationRepository; @@ -98,7 +98,7 @@ private List getBackupsToDo(Map r.backupConfigurationId() == backup.getId() && - r.status() == backupmanager.Enums.BackupStatusEnum.IN_PROGRESS); + r.status() == backupmanager.Enums.BackupStatus.IN_PROGRESS); if (!alreadyRunning && maxBackupsToAdd > 0 @@ -117,7 +117,7 @@ private void executeBackups(List backups) { try { for (ConfigurationBackup backup : backups) { ZippingContext context = ZippingContext.create(backup, trayIcon.getTrayIcon(), null, null, null, null); - BackupOperations.singleBackup(context, BackupTriggeredEnum.SCHEDULER); + BackupOperations.singleBackup(context, BackupTriggerType.SCHEDULER); } } finally { logger.info("All backups completed. Resetting isBackingUp flag."); diff --git a/src/main/java/backupmanager/Services/RunningBackupService.java b/src/main/java/backupmanager/Services/RunningBackupService.java index 9479b965..6cf72c4a 100644 --- a/src/main/java/backupmanager/Services/RunningBackupService.java +++ b/src/main/java/backupmanager/Services/RunningBackupService.java @@ -6,7 +6,7 @@ import backupmanager.Entities.BackupRequest; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Enums.BackupStatusEnum; +import backupmanager.Enums.BackupStatus; import backupmanager.Helpers.SqlHelper; import backupmanager.Utils.FolderUtils; import backupmanager.database.Repositories.BackupConfigurationRepository; @@ -20,7 +20,7 @@ public static Optional getRunningBackupByName(String backupName) List running = BackupRequestRepository.getRunningBackups(); return running.stream() .filter(r -> r.backupConfigurationId() == config.getId() - && r.status() == BackupStatusEnum.IN_PROGRESS) + && r.status() == BackupStatus.IN_PROGRESS) .findFirst(); } @@ -31,7 +31,7 @@ public static void updateBackupZippedFolderSizeById(int requestId, String pathFo public static void updateBackupStatusAfterForceTerminationByBackupConfigurationId(int backupConfigurationId) { BackupRequest request = BackupRequestRepository.getLastBackupInProgressByConfigurationId(backupConfigurationId); - BackupRequestRepository.updateRequestStatusByRequestId(request.backupRequestId(), BackupStatusEnum.TERMINATED); + BackupRequestRepository.updateRequestStatusByRequestId(request.backupRequestId(), BackupStatus.TERMINATED); } public static void updateBackupStatusAfterCompletitionByBackupConfigurationId(int backupConfigurationId) { @@ -40,7 +40,7 @@ public static void updateBackupStatusAfterCompletitionByBackupConfigurationId(in LocalDateTime completionDate = LocalDateTime.now(); long duration = SqlHelper.toMilliseconds(completionDate) - SqlHelper.toMilliseconds(request.startedDate()); - BackupRequest newRequest = new BackupRequest(request.backupRequestId(), request.backupConfigurationId(), request.startedDate(), completionDate, BackupStatusEnum.FINISHED, 100, request.triggeredBy(), duration, request.outputPath(), request.unzippedTargetSize(), request.zippedTargetSize(), request.filesCount(), request.errorMessage()); + BackupRequest newRequest = new BackupRequest(request.backupRequestId(), request.backupConfigurationId(), request.startedDate(), completionDate, BackupStatus.FINISHED, 100, request.triggeredBy(), duration, request.outputPath(), request.unzippedTargetSize(), request.zippedTargetSize(), request.filesCount(), request.errorMessage()); BackupRequestRepository.updateBackupRequestByRequestId(request.backupRequestId(), newRequest); } diff --git a/src/main/java/backupmanager/Services/ZippingThread.java b/src/main/java/backupmanager/Services/ZippingThread.java index 3aeb545d..743d800f 100644 --- a/src/main/java/backupmanager/Services/ZippingThread.java +++ b/src/main/java/backupmanager/Services/ZippingThread.java @@ -19,7 +19,7 @@ import backupmanager.BackupOperations; import backupmanager.Entities.ZippingContext; -import backupmanager.Enums.ErrorTypes; +import backupmanager.Enums.ErrorType; import backupmanager.ZipFileVisitor; public class ZippingThread { @@ -34,7 +34,7 @@ public static void zipDirectory(File sourceFile, File outputFile, ZippingContext String outupZipPath = outputFile.getAbsolutePath(); if (!sourceFile.exists()) { - handleError("Source directory does not exist: " + sourceDirectoryPath, ErrorTypes.ZippingIOError, context); + handleError("Source directory does not exist: " + sourceDirectoryPath, ErrorType.ZippingIOError, context); return; } @@ -57,14 +57,14 @@ public static void zipDirectory(File sourceFile, File outputFile, ZippingContext } catch (IOException e) { logger.error("I/O error occurred while zipping directory \"" + sourceDirectoryPath + "\"" + e.getMessage(), e); - handleError("I/O error occurred", ErrorTypes.ZippingIOError, context); + handleError("I/O error occurred", ErrorType.ZippingIOError, context); } finally { finalizeProcess(context); } }); } - private static void handleError(String message, ErrorTypes errorType, ZippingContext context) { + private static void handleError(String message, ErrorType errorType, ZippingContext context) { logger.error(message); BackupOperations.setError(errorType, context.trayIcon(), null); BackupOperations.reEnableButtonsAndTable(context); diff --git a/src/main/java/backupmanager/database/Repositories/BackupRequestRepository.java b/src/main/java/backupmanager/database/Repositories/BackupRequestRepository.java index 89c2701d..451add3d 100644 --- a/src/main/java/backupmanager/database/Repositories/BackupRequestRepository.java +++ b/src/main/java/backupmanager/database/Repositories/BackupRequestRepository.java @@ -12,8 +12,8 @@ import org.slf4j.LoggerFactory; import backupmanager.Entities.BackupRequest; -import backupmanager.Enums.BackupStatusEnum; -import backupmanager.Enums.BackupTriggeredEnum; +import backupmanager.Enums.BackupStatus; +import backupmanager.Enums.BackupTriggerType; import backupmanager.Helpers.SqlHelper; import backupmanager.database.Database; @@ -80,7 +80,7 @@ public static List getRunningBackups() { Connection conn = Database.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { - BackupStatusEnum status = BackupStatusEnum.IN_PROGRESS; + BackupStatus status = BackupStatus.IN_PROGRESS; stmt.setInt(1, status.getCode()); try (ResultSet rs = stmt.executeQuery()) { @@ -100,7 +100,7 @@ public static List getRunningBackups() { LocalDateTime startedDate = SqlHelper.toLocalDateTime(startedDateMills); LocalDateTime completionDate = SqlHelper.toLocalDateTime(completionDateStr); - BackupTriggeredEnum triggeredBy = BackupTriggeredEnum.fromCode(triggeredByInt); + BackupTriggerType triggeredBy = BackupTriggerType.fromCode(triggeredByInt); backups.add(new BackupRequest(backupRequestId, backupConfigurationId, startedDate, completionDate, status, progress, triggeredBy, durationMs, outputPath, unzippedTargetSize, zippedTargetSize, filesCount, errorMessage)); logger.debug("Loaded running backup: backupRequestId={} configurationId={}", backupRequestId, backupConfigurationId); @@ -159,8 +159,8 @@ public static List getRequestBackups() { LocalDateTime startedDate = SqlHelper.toLocalDateTime(startedDateMills); LocalDateTime completionDate = SqlHelper.toLocalDateTime(completionDateStr); - BackupTriggeredEnum triggeredBy = BackupTriggeredEnum.fromCode(triggeredByInt); - BackupStatusEnum status = BackupStatusEnum.fromCode(statusInt); + BackupTriggerType triggeredBy = BackupTriggerType.fromCode(triggeredByInt); + BackupStatus status = BackupStatus.fromCode(statusInt); backups.add(new BackupRequest(backupRequestId, backupConfigurationId, startedDate, completionDate, status, progress, triggeredBy, durationMs, outputPath, unzippedTargetSize, zippedTargetSize, filesCount, errorMessage)); logger.debug("Loaded running backup: backupRequestId={} configurationId={}", backupRequestId, backupConfigurationId); @@ -194,7 +194,7 @@ public static boolean isAnyBackupRunning() { } } - public static void updateRequestStatusByRequestId(int backupRequestId, BackupStatusEnum status) { + public static void updateRequestStatusByRequestId(int backupRequestId, BackupStatus status) { String sql = """ UPDATE BackupRequests @@ -355,8 +355,8 @@ public static BackupRequest getLastBackupInProgressByConfigurationId(int configu LocalDateTime startedDate = SqlHelper.toLocalDateTime(startedDateMills); LocalDateTime completionDate = SqlHelper.toLocalDateTime(completionDateStr); - BackupStatusEnum status = BackupStatusEnum.fromCode(statusInt); - BackupTriggeredEnum triggeredBy = BackupTriggeredEnum.fromCode(triggeredByInt); + BackupStatus status = BackupStatus.fromCode(statusInt); + BackupTriggerType triggeredBy = BackupTriggerType.fromCode(triggeredByInt); return new BackupRequest(backupRequestId, configurationId, startedDate, completionDate, status, progress, triggeredBy, durationMs, outputPath, unzippedTargetSize, zippedTargetSize, filesCount, errorMessage); } else { @@ -414,8 +414,8 @@ public static BackupRequest getBackupRequestById(int requestId) { LocalDateTime startedDate = SqlHelper.toLocalDateTime(startedDateMills); LocalDateTime completionDate = SqlHelper.toLocalDateTime(completionDateStr); - BackupStatusEnum status = BackupStatusEnum.fromCode(statusInt); - BackupTriggeredEnum triggeredBy = BackupTriggeredEnum.fromCode(triggeredByInt); + BackupStatus status = BackupStatus.fromCode(statusInt); + BackupTriggerType triggeredBy = BackupTriggerType.fromCode(triggeredByInt); return new BackupRequest(requestId, backupConfigurationId, startedDate, completionDate, status, progress, triggeredBy, durationMs, outputPath, unzippedTargetSize, zippedTargetSize, filesCount, errorMessage); } else { diff --git a/src/main/java/backupmanager/database/Repositories/EmailRepository.java b/src/main/java/backupmanager/database/Repositories/EmailRepository.java index 4bbd87d7..088ec003 100644 --- a/src/main/java/backupmanager/database/Repositories/EmailRepository.java +++ b/src/main/java/backupmanager/database/Repositories/EmailRepository.java @@ -36,7 +36,7 @@ public static void insertEmail(Email email) { } } - public static Email getLastEmail() { + public static Email getLastEmailByType(EmailType type) { String sql = """ SELECT EmailId, @@ -46,22 +46,24 @@ public static Email getLastEmail() { Payload FROM Emails + WHERE + Type = ? ORDER BY InsertDate DESC """; try (Connection conn = Database.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)){ + stmt.setInt(1, type.getCode()); + try (ResultSet rs = stmt.executeQuery()){ if (rs.next()) { int emailId = rs.getInt("EmailId"); - int typeInt = rs.getInt("Type"); long insertDateLong = rs.getLong("InsertDate"); String appVersion = rs.getString("AppVersion"); String payload = rs.getString("Payload"); LocalDateTime startedDate = SqlHelper.toLocalDateTime(insertDateLong); - EmailType type = EmailType.fromCode(typeInt); return new Email(emailId, type, startedDate, appVersion, payload); } else { diff --git a/src/main/resources/db/002_seed.sql b/src/main/resources/db/002_seed.sql index e3551ce1..6833c3e3 100644 --- a/src/main/resources/db/002_seed.sql +++ b/src/main/resources/db/002_seed.sql @@ -3,4 +3,4 @@ INSERT OR IGNORE INTO Configurations (Code, Value) VALUES ('Theme', 'light'), ('SubscriptionNedded', 'False'); -INSERT OR IGNORE INTO SchemaVersion VALUES (1); \ No newline at end of file +INSERT OR IGNORE INTO SchemaVersion VALUES (1); diff --git a/src/main/resources/res/config/config.json b/src/main/resources/res/config/config.json index 02b0a319..968294dc 100644 --- a/src/main/resources/res/config/config.json +++ b/src/main/resources/res/config/config.json @@ -38,16 +38,21 @@ "BackupService": { "value": 1, "type": "int", - "description": "Interval in minutes to check for auto backup" + "description": "Interval in minutes to check for auto backup." }, "MaxCountForSameBackup": { "value": 1, "type": "int", - "description": "Max count for the same backup in the same directory" + "description": "Max count for the same backup in the same directory." }, "SubscriptionWarningDays": { "value": 7, "type": "int", - "description": "Number of days before subscription expiration to show a warning" + "description": "Number of days before subscription expiration to show a warning." + }, + "CriticalEmailMinWaitDays": { + "value": 7, + "type": "int", + "description": "Minimum number of days that must elapse before the same user can trigger another critical error email." } -} \ No newline at end of file +} diff --git a/src/test/java/test/repositories/BackupRequestRepositoryTest.java b/src/test/java/test/repositories/BackupRequestRepositoryTest.java index 1e47f1dd..d6ae4ec6 100644 --- a/src/test/java/test/repositories/BackupRequestRepositoryTest.java +++ b/src/test/java/test/repositories/BackupRequestRepositoryTest.java @@ -13,8 +13,8 @@ import backupmanager.Entities.BackupRequest; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Enums.BackupStatusEnum; -import backupmanager.Enums.BackupTriggeredEnum; +import backupmanager.Enums.BackupStatus; +import backupmanager.Enums.BackupTriggerType; import backupmanager.database.Database; import backupmanager.database.DatabasePaths; import backupmanager.database.Repositories.BackupConfigurationRepository; @@ -59,7 +59,7 @@ protected void updateRequestFolderSizeZippedByRequestId_shouldBeTrue_forDifferen @Test protected void updateRequestStatusByRequestId_shouldBeTrue_forDifferentStatusAfterUpdate() { - BackupRequestRepository.updateRequestStatusByRequestId(requests.get(2).backupRequestId(), BackupStatusEnum.FINISHED); + BackupRequestRepository.updateRequestStatusByRequestId(requests.get(2).backupRequestId(), BackupStatus.FINISHED); BackupRequest runningRequest = BackupRequestRepository.getBackupRequestById(requests.get(2).backupRequestId()); assertTrue(requests.get(2).status() != runningRequest.status()); } @@ -75,7 +75,7 @@ private void setupBackupRequestList() { BackupRequest request1 = createFinishedRequest(backups.get(0).getId()); BackupRequest request2 = createFinishedRequest(backups.get(1).getId()); - BackupRequest request3 = BackupRequest.createNewBackupRequest(backups.get(1).getId(), BackupTriggeredEnum.USER, "outputPath", 123412, 2); + BackupRequest request3 = BackupRequest.createNewBackupRequest(backups.get(1).getId(), BackupTriggerType.USER, "outputPath", 123412, 2); BackupRequest request4 = createFinishedRequest(backups.get(1).getId()); requests = new ArrayList<>(); @@ -113,6 +113,6 @@ private ConfigurationBackup createConfiguration(String name) { } private BackupRequest createFinishedRequest(int configurationId) { - return new BackupRequest(0, configurationId, LocalDateTime.now(), LocalDateTime.now(), BackupStatusEnum.FINISHED, 100, BackupTriggeredEnum.SCHEDULER, Long.valueOf(10), "OutputPath", 1000, Long.valueOf(100), 2, ""); + return new BackupRequest(0, configurationId, LocalDateTime.now(), LocalDateTime.now(), BackupStatus.FINISHED, 100, BackupTriggerType.SCHEDULER, Long.valueOf(10), "OutputPath", 1000, Long.valueOf(100), 2, ""); } } diff --git a/src/test/java/test/repositories/EmailRepositoryTest.java b/src/test/java/test/repositories/EmailRepositoryTest.java index c7fad55c..fb2398b7 100644 --- a/src/test/java/test/repositories/EmailRepositoryTest.java +++ b/src/test/java/test/repositories/EmailRepositoryTest.java @@ -35,7 +35,7 @@ protected void clean() throws IOException { @Test protected void getLastEmail_shouldBeTrue_forLastEmailRegistered() { - Email lastSent = EmailRepository.getLastEmail(); + Email lastSent = EmailRepository.getLastEmailByType(EmailType.CRITICAL_ERROR); assertEquals("a strange error", lastSent.payload()); } From 80b157191f0bd955252ea4b89867bae8e7888578 Mon Sep 17 00:00:00 2001 From: DennisTurco Date: Tue, 17 Feb 2026 17:02:34 +0100 Subject: [PATCH 2/2] update --- .../java/backupmanager/Email/EmailSender.java | 40 ++++++++++++---- .../Managers/ExceptionManager.java | 2 +- .../Repositories/EmailRepository.java | 46 ++++++++++++++++++- src/main/resources/res/config/config.json | 2 +- .../repositories/EmailRepositoryTest.java | 13 ++++-- 5 files changed, 87 insertions(+), 16 deletions(-) diff --git a/src/main/java/backupmanager/Email/EmailSender.java b/src/main/java/backupmanager/Email/EmailSender.java index 9af56d66..95baf20f 100644 --- a/src/main/java/backupmanager/Email/EmailSender.java +++ b/src/main/java/backupmanager/Email/EmailSender.java @@ -47,7 +47,7 @@ public class EmailSender { * @param subject The email subject. * @param body The email body. */ - public static void sendErrorEmail(String subject, String body) { + public static void sendErrorEmail(String subject, String body, String errorMessage) { User user = getCurrentUser(); if (user == null) { @@ -55,8 +55,7 @@ public static void sendErrorEmail(String subject, String body) { return; } - if (!canSend()) { - logger.info("A critical error occurred, but the email cannot be sent because insufficient time has passed since the last one."); + if (!canSend(errorMessage)) { return; } @@ -89,7 +88,7 @@ public static void sendErrorEmail(String subject, String body) { logger.info("Error email sent with subject: " + subject); - insertEmailInternally(EmailType.CRITICAL_ERROR, null); + insertEmailInternally(EmailType.CRITICAL_ERROR, errorMessage); } /** @@ -135,15 +134,36 @@ private static void insertEmailInternally(EmailType type, String payload) { EmailRepository.insertEmail(email); } - private static boolean canSend() { - int minWait = configReader.getConfigValue("CriticalEmailMinWaitDays", 7); + private static boolean canSend(String payload) { + int minWaitDays = configReader.getConfigValue("CriticalEmailMinWaitDays", 7); LocalDateTime now = LocalDateTime.now(); - Email email = EmailRepository.getLastEmailByType(EmailType.CRITICAL_ERROR); - if (email == null) return true; - LocalDateTime lastSent = email.insertDate(); + if (isDuplicateError(payload)) { + logger.info("A critical error occurred, but the email was not sent because this error has already been reported for the current version."); + return false; + } + + if (!hasWaitedSufficientTime(minWaitDays, now)) { + logger.info("A critical error occurred, but the email was not sent because the minimum wait time since the last critical error email has not elapsed."); + return false; + } + + return true; + } + + private static boolean isDuplicateError(String payload) { + if (payload == null) return false; + + Email lastError = EmailRepository.getLastErrorEmailByPayloadAndVersion(payload, ConfigKey.VERSION.getValue()); + return lastError != null; + } + + private static boolean hasWaitedSufficientTime(int minWaitDays, LocalDateTime now) { + Email lastEmail = EmailRepository.getLastEmailByType(EmailType.CRITICAL_ERROR); + if (lastEmail == null) return true; - return (lastSent.plusDays(minWait).isBefore(now)); + LocalDateTime lastSent = lastEmail.insertDate(); + return lastSent.plusDays(minWaitDays).isBefore(now); } private static String getTextFromLogFile(int rows) { diff --git a/src/main/java/backupmanager/Managers/ExceptionManager.java b/src/main/java/backupmanager/Managers/ExceptionManager.java index c8b43d40..a6af2c8a 100644 --- a/src/main/java/backupmanager/Managers/ExceptionManager.java +++ b/src/main/java/backupmanager/Managers/ExceptionManager.java @@ -28,7 +28,7 @@ public static void openExceptionMessage(String errorMessage, String stackTrace) stackTrace = !errorMessage.isEmpty() ? errorMessage + "\n" + stackTrace : errorMessage + stackTrace; - EmailSender.sendErrorEmail("Critical Error Report", stackTrace); + EmailSender.sendErrorEmail("Critical Error Report", stackTrace, errorMessage); String stackTraceMessage = TranslationCategory.DIALOGS.getTranslation(TranslationKey.EXCEPTION_MESSAGE_REPORT_MESSAGE) + "\n" + stackTrace; diff --git a/src/main/java/backupmanager/database/Repositories/EmailRepository.java b/src/main/java/backupmanager/database/Repositories/EmailRepository.java index 088ec003..2462cdcd 100644 --- a/src/main/java/backupmanager/database/Repositories/EmailRepository.java +++ b/src/main/java/backupmanager/database/Repositories/EmailRepository.java @@ -71,7 +71,51 @@ public static Email getLastEmailByType(EmailType type) { } } } catch (SQLException e) { - logger.error("Failed to fetch last email", e); + logger.error("Failed to fetch last email by type", e); + } + + return null; + } + + public static Email getLastErrorEmailByPayloadAndVersion(String payload, String version) { + String sql = """ + SELECT + EmailId, + Type, + InsertDate, + AppVersion, + Payload + FROM + Emails + WHERE + Type = 2 -- critical error + AND Payload = ? + AND AppVersion = ? + ORDER BY InsertDate DESC + """; + + try (Connection conn = Database.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)){ + + stmt.setString(1, payload); + stmt.setString(2, version); + + try (ResultSet rs = stmt.executeQuery()){ + if (rs.next()) { + int emailId = rs.getInt("EmailId"); + int typeInt = rs.getInt("Type"); + long insertDateLong = rs.getLong("InsertDate"); + + LocalDateTime startedDate = SqlHelper.toLocalDateTime(insertDateLong); + EmailType type = EmailType.fromCode(typeInt); + + return new Email(emailId, type, startedDate, version, payload); + } else { + logger.debug("No email to obtain"); + } + } + } catch (SQLException e) { + logger.error("Failed to fetch last email by version and payload", e); } return null; diff --git a/src/main/resources/res/config/config.json b/src/main/resources/res/config/config.json index 968294dc..fbba5d06 100644 --- a/src/main/resources/res/config/config.json +++ b/src/main/resources/res/config/config.json @@ -12,7 +12,7 @@ "EMAIL": "assistenza@shardpc.it", "SHARD_WEBSITE": "https://www.shardpc.it/", "LOGO_IMG": "/res/img/logo.png", - "VERSION": "2.1.0", + "VERSION": "2.2.0", "GUI_WIDTH": "982", "GUI_HEIGHT": "715", diff --git a/src/test/java/test/repositories/EmailRepositoryTest.java b/src/test/java/test/repositories/EmailRepositoryTest.java index fb2398b7..761a54e9 100644 --- a/src/test/java/test/repositories/EmailRepositoryTest.java +++ b/src/test/java/test/repositories/EmailRepositoryTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.AfterEach; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -34,9 +35,15 @@ protected void clean() throws IOException { } @Test - protected void getLastEmail_shouldBeTrue_forLastEmailRegistered() { - Email lastSent = EmailRepository.getLastEmailByType(EmailType.CRITICAL_ERROR); - assertEquals("a strange error", lastSent.payload()); + protected void getLastEmailByType_shouldBeEquals_forLastWelcomeEmailRegistered() { + Email lastSent = EmailRepository.getLastEmailByType(EmailType.WELCOME); + assertEquals(EmailType.WELCOME, lastSent.type()); + } + + @Test + protected void getLastErrorEmailByPayloadAndVersion_shouldBeTrue_forLastErrorEmailRegisteredByData() { + Email lastSent = EmailRepository.getLastErrorEmailByPayloadAndVersion("thread1 error", "2.1.0"); + assertTrue(lastSent != null); } private void createEmails() {