From 59ed1989942ef16c530322102b45d65bdf46d79c Mon Sep 17 00:00:00 2001 From: Stefan Date: Sat, 1 Nov 2025 14:25:32 +0100 Subject: [PATCH 01/44] Update the scan calls to send maxHashExtractionFileSize, for now hardcoded --- .../pwss/model/request/scan/StartScanAllRequest.java | 9 +++++++++ .../model/request/scan/StartSingleScanRequest.java | 3 ++- src/main/java/org/pwss/service/ScanService.java | 12 +++++++++--- src/main/java/org/pwss/service/network/Endpoint.java | 2 +- 4 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/pwss/model/request/scan/StartScanAllRequest.java diff --git a/src/main/java/org/pwss/model/request/scan/StartScanAllRequest.java b/src/main/java/org/pwss/model/request/scan/StartScanAllRequest.java new file mode 100644 index 0000000..c12e6c0 --- /dev/null +++ b/src/main/java/org/pwss/model/request/scan/StartScanAllRequest.java @@ -0,0 +1,9 @@ +package org.pwss.model.request.scan; + +/** + * Represents a request to start a scan for all files. + * + * @param maxHashExtractionFileSize The maximum file size (in bytes) for which hash extraction should be performed. + */ +public record StartScanAllRequest(long maxHashExtractionFileSize) { +} diff --git a/src/main/java/org/pwss/model/request/scan/StartSingleScanRequest.java b/src/main/java/org/pwss/model/request/scan/StartSingleScanRequest.java index 00c3a53..c6c01dd 100644 --- a/src/main/java/org/pwss/model/request/scan/StartSingleScanRequest.java +++ b/src/main/java/org/pwss/model/request/scan/StartSingleScanRequest.java @@ -4,6 +4,7 @@ * Request to start a single scan by a monitored directory's ID. * * @param id The ID of the monitored directory to scan. + * @param maxHashExtractionFileSize The maximum file size (in bytes) for which hash extraction should be performed. */ -public record StartSingleScanRequest(long id) { +public record StartSingleScanRequest(long id, long maxHashExtractionFileSize) { } diff --git a/src/main/java/org/pwss/service/ScanService.java b/src/main/java/org/pwss/service/ScanService.java index c932563..a9eb38d 100644 --- a/src/main/java/org/pwss/service/ScanService.java +++ b/src/main/java/org/pwss/service/ScanService.java @@ -18,6 +18,7 @@ import org.pwss.model.entity.Scan; import org.pwss.model.request.scan.GetMostRecentScansRequest; import org.pwss.model.request.scan.GetScanDiffsRequest; +import org.pwss.model.request.scan.StartScanAllRequest; import org.pwss.model.request.scan.StartSingleScanRequest; import org.pwss.model.response.LiveFeedResponse; import org.pwss.service.network.Endpoint; @@ -44,8 +45,11 @@ public ScanService() { * @throws ExecutionException If an error occurs during the asynchronous execution of the request. * @throws InterruptedException If the thread executing the request is interrupted. */ - public boolean startScan() throws StartFullScanException, ExecutionException, InterruptedException { - HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.START_SCAN, null); + public boolean startScan() throws StartFullScanException, ExecutionException, InterruptedException, JsonProcessingException { + // Fake properties read + long maxHashExtractionFileSize = 1024 * 1024 * 2047; // 2GB - 1MB + String body = objectMapper.writeValueAsString(new StartScanAllRequest(maxHashExtractionFileSize)); + HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.START_SCAN, body); return switch (response.statusCode()) { case 200 -> true; @@ -71,7 +75,9 @@ public boolean startScan() throws StartFullScanException, ExecutionException, In * @throws JsonProcessingException If an error occurs while serializing the start scan request to JSON. */ public boolean startScanById(long id) throws StartScanByIdException, ExecutionException, InterruptedException, JsonProcessingException { - String body = objectMapper.writeValueAsString(new StartSingleScanRequest(id)); + // Fake properties read + long maxHashExtractionFileSize = 1024 * 1024 * 2047; // 2GB - 1MB + String body = objectMapper.writeValueAsString(new StartSingleScanRequest(id, maxHashExtractionFileSize)); HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.START_SCAN_ID, body); return switch (response.statusCode()) { diff --git a/src/main/java/org/pwss/service/network/Endpoint.java b/src/main/java/org/pwss/service/network/Endpoint.java index c6b6ef3..a02c421 100644 --- a/src/main/java/org/pwss/service/network/Endpoint.java +++ b/src/main/java/org/pwss/service/network/Endpoint.java @@ -25,7 +25,7 @@ public enum Endpoint { /** * Endpoint for starting a file integrity scan of all directories. */ - START_SCAN(HTTP_Method.GET, A.BASE_URL + A.SCAN + "start/all"), + START_SCAN(HTTP_Method.POST, A.BASE_URL + A.SCAN + "start/all"), /** * Endpoint for starting a file integrity scan of a specific directory. */ From db3d9fb4ddf267d0ebfd5b03cdf436a764748d8a Mon Sep 17 00:00:00 2001 From: Stefan Date: Sat, 1 Nov 2025 15:17:10 +0100 Subject: [PATCH 02/44] Added max hash extraction file size config value --- .../java/org/pwss/app_settings/AppConfig.java | 19 +++++ .../org/pwss/app_settings/ConfigLoader.java | 77 ++++++++++++++++++- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/pwss/app_settings/AppConfig.java b/src/main/java/org/pwss/app_settings/AppConfig.java index c967c4d..22f2e26 100644 --- a/src/main/java/org/pwss/app_settings/AppConfig.java +++ b/src/main/java/org/pwss/app_settings/AppConfig.java @@ -27,6 +27,12 @@ public final class AppConfig { * when the class is initialized. */ public final static String LICENSE_KEY; + /** + * Maximum file size (in bytes) for hash extraction. This value is loaded from + * the configuration file + * when the class is initialized. + */ + public final static long MAX_HASH_EXTRACTION_FILE_SIZE; /** * ConfigLoader instance used to load and manage configuration values. @@ -41,6 +47,7 @@ public final class AppConfig { USE_SPLASH_SCREEN = configLoader.isUseSplashScreen(); APP_THEME = configLoader.getAppTheme(); LICENSE_KEY = configLoader.getLicenseKey(); + MAX_HASH_EXTRACTION_FILE_SIZE = configLoader.getHashExtractionMaxFileSizeValue(); } /** @@ -80,4 +87,16 @@ public static final boolean setLicenseKey(String licenseKey) { return configLoader.setLicenseKey(licenseKey); } + /** + * Sets the maximum file size for hash extraction in the configuration file. + * This change will take effect only after + * the frontend application is restarted. + * + * @param maxFileSize The value to set for the maximum file size (in bytes) + * @return true if setting was successful, otherwise false + */ + public static final boolean setMaxHashExtractionFileSize(long maxFileSize) { + return configLoader.setMaxHashExtractionFileSize(String.valueOf(maxFileSize)); + } + } diff --git a/src/main/java/org/pwss/app_settings/ConfigLoader.java b/src/main/java/org/pwss/app_settings/ConfigLoader.java index 5ced67d..f197389 100644 --- a/src/main/java/org/pwss/app_settings/ConfigLoader.java +++ b/src/main/java/org/pwss/app_settings/ConfigLoader.java @@ -29,6 +29,10 @@ final class ConfigLoader { * Key in the properties file for license key setting. */ private final String LICENSE_KEY = "frontend.licensekey"; + /** + * Key in the properties file for maximum hash extraction file size setting. + */ + private final String MAX_HASH_EXTRACTION_FILE_SIZE_KEY = "scanner.max_hash_extraction_file_size"; /** * Path to the configuration file. @@ -52,6 +56,10 @@ final class ConfigLoader { * License key for the application. */ private final String licenseKey; + /** + * Maximum hash extraction file size. + */ + private final long maxHashExtractionFileSize; /** * Constructor that loads configuration settings from the properties file and @@ -67,10 +75,12 @@ final class ConfigLoader { this.useSplashScreen = true; this.appTheme = 1; this.licenseKey = "none"; + this.maxHashExtractionFileSize = -1; } else { this.useSplashScreen = getSplashScreenFlagFromConfigString(getSplashScreenProperty()); this.appTheme = getAppThemeValueFromConfigString(getAppThemeProperty()); this.licenseKey = getLicenseKeyProperty(); + this.maxHashExtractionFileSize = getMaxHashExtractionFileSizeFromConfigString(getMaxHashExtractionFileSizeProperty()); } } @@ -79,7 +89,7 @@ final class ConfigLoader { * Parses the splash screen flag from a configuration string. * * @param configFileString The configuration string to be parsed - * @return falsee if the string is "false" (case insensitive), otherwise true + * @return false if the string is "false" (case insensitive), otherwise true */ private final boolean getSplashScreenFlagFromConfigString(String configFileString) { @@ -124,6 +134,29 @@ private final int getAppThemeValueFromConfigString(String configFileString) { } + /** + * Parses the maximum hash extraction file size from a configuration string. + * + * @return The long value of the maximum hash extraction file size, or -1 if + * parsing fails or the value is invalid + */ + private final long getMaxHashExtractionFileSizeFromConfigString(String configFileString) { + try { + long maxHashExtractionFileSizeValue = Long.parseLong(configFileString); + + if (maxHashExtractionFileSizeValue >= -1) + return maxHashExtractionFileSizeValue; + else + return -1; + } + + catch (Exception exception) { + log.debug("Could not parse max hash extraction file size value from app settings", exception); + log.error("Could not parse max hash extraction file size value from app settings {}", exception.getMessage()); + return -1; + } + } + /** * Loads the configuration properties from the file specified by * CONFIG_FILE_PATH. @@ -200,6 +233,23 @@ private final String getLicenseKeyProperty() { } + /** + * Retrieves the maximum hash extraction file size property value from the + * properties file. + * + * @return The maximum hash extraction file size property value, or "-1" if an + * error occurs + */ + private final String getMaxHashExtractionFileSizeProperty() { + try { + return properties.getProperty(MAX_HASH_EXTRACTION_FILE_SIZE_KEY); + } catch (Exception exception) { + log.debug("Max hash extraction file size property could not be fetched", exception); + log.error("Max hash extraction file size property could not be fetched {}", exception.getMessage()); + return "-1"; + } + } + /** * Sets the splash screen flag in the properties file. * @@ -260,6 +310,27 @@ final boolean setLicenseKey(String licenseKey) { } } + /** + * Sets the maximum hash extraction file size value in the properties file. + * + * @param maxHashExtractionFileSize The value to set for the maximum hash + * extraction file size + * @return true if setting was successful, otherwise false + */ + final boolean setMaxHashExtractionFileSize(String maxHashExtractionFileSize) { + + properties.setProperty(MAX_HASH_EXTRACTION_FILE_SIZE_KEY, maxHashExtractionFileSize); + + try (FileOutputStream fos = new FileOutputStream(CONFIG_FILE_PATH)) { + properties.store(fos, null); + return true; + } catch (Exception exception) { + log.debug("Max hash extraction file size could not be set in app.config file", exception); + log.error("Max hash extraction file size could not be set in app.config file", exception.getMessage()); + return false; + } + } + /** * Gets the value indicating whether to use splash screen or not. * @@ -286,4 +357,8 @@ final int getAppTheme() { final String getLicenseKey() { return licenseKey; } + + final long getHashExtractionMaxFileSizeValue() { + return maxHashExtractionFileSize; + } } \ No newline at end of file From 4fb1bf42c4f8b9a46961c1b6f2fd763dc2e29cd3 Mon Sep 17 00:00:00 2001 From: Stefan Date: Sat, 1 Nov 2025 15:21:08 +0100 Subject: [PATCH 03/44] Send actual config value in the requests --- app_storage/metadata/metadata.temp | 2 +- options/app.config | 3 ++- src/main/java/org/pwss/service/ScanService.java | 11 +++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app_storage/metadata/metadata.temp b/app_storage/metadata/metadata.temp index ca44c66..124a382 100644 --- a/app_storage/metadata/metadata.temp +++ b/app_storage/metadata/metadata.temp @@ -1 +1 @@ -#Wed Oct 15 22:29:09 CEST 2025 +#Thu Oct 30 19:28:32 CET 2025 diff --git a/options/app.config b/options/app.config index 8ba896f..61aeeb3 100644 --- a/options/app.config +++ b/options/app.config @@ -1,4 +1,5 @@ -#Sat Oct 18 15:00:03 CEST 2025 +#Sat Nov 01 14:24:19 CET 2025 frontend.licensekey= frontend.splashscreen=false frontend.theme=1 +scanner.max_hash_extraction_file_size=-1 \ No newline at end of file diff --git a/src/main/java/org/pwss/service/ScanService.java b/src/main/java/org/pwss/service/ScanService.java index a9eb38d..c297f41 100644 --- a/src/main/java/org/pwss/service/ScanService.java +++ b/src/main/java/org/pwss/service/ScanService.java @@ -6,6 +6,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; +import org.pwss.app_settings.AppConfig; import org.pwss.exception.scan.GetAllMostRecentScansException; import org.pwss.exception.scan.GetMostRecentScansException; import org.pwss.exception.scan.GetScanDiffsException; @@ -46,8 +47,9 @@ public ScanService() { * @throws InterruptedException If the thread executing the request is interrupted. */ public boolean startScan() throws StartFullScanException, ExecutionException, InterruptedException, JsonProcessingException { - // Fake properties read - long maxHashExtractionFileSize = 1024 * 1024 * 2047; // 2GB - 1MB + // Read max hash extraction file size from config + long maxHashExtractionFileSize = AppConfig.MAX_HASH_EXTRACTION_FILE_SIZE; + String body = objectMapper.writeValueAsString(new StartScanAllRequest(maxHashExtractionFileSize)); HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.START_SCAN, body); @@ -75,8 +77,9 @@ public boolean startScan() throws StartFullScanException, ExecutionException, In * @throws JsonProcessingException If an error occurs while serializing the start scan request to JSON. */ public boolean startScanById(long id) throws StartScanByIdException, ExecutionException, InterruptedException, JsonProcessingException { - // Fake properties read - long maxHashExtractionFileSize = 1024 * 1024 * 2047; // 2GB - 1MB + // Read max hash extraction file size from config + long maxHashExtractionFileSize = AppConfig.MAX_HASH_EXTRACTION_FILE_SIZE; + String body = objectMapper.writeValueAsString(new StartSingleScanRequest(id, maxHashExtractionFileSize)); HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.START_SCAN_ID, body); From 863cfaf0fa2f77ee2af573e1ad8e8bf7c27cb06e Mon Sep 17 00:00:00 2001 From: Stefan Date: Sat, 1 Nov 2025 15:40:23 +0100 Subject: [PATCH 04/44] GUI progress, storing settings left :) --- .../org/pwss/controller/HomeController.java | 6 ++ .../java/org/pwss/view/screen/HomeScreen.form | 39 ++++++++++- .../java/org/pwss/view/screen/HomeScreen.java | 64 ++++++++++++++++--- 3 files changed, 100 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index 6482af7..ae645bb 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -411,6 +411,12 @@ public void mouseClicked(MouseEvent e) { } }); + screen.getMaxHashExctractionFileSizeSlider().addChangeListener(l -> { + int value = screen.getMaxHashExctractionFileSizeSlider().getValue(); + log.debug("Setting max hash extraction file size to {} MB", value); + screen.getMaxHashExtractionFileSizeValueLabel().setText(value + " MB"); + // TODO: Update the AppConfig value + }); } @Override diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.form b/src/main/java/org/pwss/view/screen/HomeScreen.form index 07a4cc5..eba3f55 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.form +++ b/src/main/java/org/pwss/view/screen/HomeScreen.form @@ -495,7 +495,7 @@ - + @@ -511,6 +511,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.java b/src/main/java/org/pwss/view/screen/HomeScreen.java index 7e137b3..a10d94d 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.java +++ b/src/main/java/org/pwss/view/screen/HomeScreen.java @@ -17,6 +17,8 @@ import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JSlider; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTable; @@ -211,6 +213,16 @@ public class HomeScreen extends BaseScreen { */ private JCheckBox showSplashScreenCheckBox; + /** + * Slider for setting the maximum file size for hash extraction. + */ + private JSlider maxHashExctractionFileSizeSlider; + + /** + * Label displaying the value of the maximum file size for hash extraction. + */ + private JLabel maxHashExtractionFileSizeValueLabel; + @Override protected String getScreenName() { return "Home"; @@ -464,6 +476,24 @@ public JTable getQuarantineTable() { return quarantineTable; } + /** + * Returns the slider for setting the maximum file size for hash extraction. + * + * @return JSlider for maximum hash extraction file size. + */ + public JLabel getMaxHashExtractionFileSizeValueLabel() { + return maxHashExtractionFileSizeValueLabel; + } + + /** + * Returns the slider for setting the maximum file size for hash extraction. + * + * @return JSlider for maximum hash extraction file size. + */ + public JSlider getMaxHashExctractionFileSizeSlider() { + return maxHashExctractionFileSizeSlider; + } + { // GUI initializer generated by IntelliJ IDEA GUI Designer // >>> IMPORTANT!! <<< @@ -657,14 +687,32 @@ public JTable getQuarantineTable() { label8.setText("© 2025 PWSS ORG"); panel5.add(label8, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JPanel panel6 = new JPanel(); - panel6.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1)); + panel6.setLayout(new GridLayoutManager(5, 1, new Insets(0, 0, 0, 0), -1, -1)); panel5.add(panel6, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); showSplashScreenCheckBox = new JCheckBox(); showSplashScreenCheckBox.setText("Show splash screen"); panel6.add(showSplashScreenCheckBox, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + maxHashExctractionFileSizeSlider = new JSlider(); + maxHashExctractionFileSizeSlider.setMajorTickSpacing(4096); + maxHashExctractionFileSizeSlider.setMaximum(102400); + maxHashExctractionFileSizeSlider.setMinimum(2048); + maxHashExctractionFileSizeSlider.setMinorTickSpacing(1024); + maxHashExctractionFileSizeSlider.setPaintLabels(false); + maxHashExctractionFileSizeSlider.setPaintTicks(true); + maxHashExctractionFileSizeSlider.setPaintTrack(true); + maxHashExctractionFileSizeSlider.setSnapToTicks(true); + panel6.add(maxHashExctractionFileSizeSlider, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label9 = new JLabel(); - label9.setText("Theme"); - panel5.add(label9, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + label9.setText("Max hash exctraction file size (MB)"); + panel6.add(label9, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JSeparator separator1 = new JSeparator(); + panel6.add(separator1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); + maxHashExtractionFileSizeValueLabel = new JLabel(); + maxHashExtractionFileSizeValueLabel.setText("Selected:"); + panel6.add(maxHashExtractionFileSizeValueLabel, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JLabel label10 = new JLabel(); + label10.setText("Theme"); + panel5.add(label10, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JPanel panel7 = new JPanel(); panel7.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); splitPane5.setRightComponent(panel7); @@ -672,11 +720,11 @@ public JTable getQuarantineTable() { panel7.add(scrollPane9, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); quarantineTable = new JTable(); scrollPane9.setViewportView(quarantineTable); - final JLabel label10 = new JLabel(); - Font label10Font = this.$$$getFont$$$(null, Font.BOLD, 14, label10.getFont()); - if (label10Font != null) label10.setFont(label10Font); - label10.setText("Quarantined files"); - panel7.add(label10, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JLabel label11 = new JLabel(); + Font label11Font = this.$$$getFont$$$(null, Font.BOLD, 14, label11.getFont()); + if (label11Font != null) label11.setFont(label11Font); + label11.setText("Quarantined files"); + panel7.add(label11, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); scanProgressContainer = new JPanel(); scanProgressContainer.setLayout(new GridLayoutManager(1, 2, new Insets(10, 10, 10, 10), -1, -1)); rootPanel.add(scanProgressContainer, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); From 503d5b57be74f389a3f6ecd951b19eefeb8ef899 Mon Sep 17 00:00:00 2001 From: Stefan Date: Sun, 2 Nov 2025 19:07:09 +0100 Subject: [PATCH 05/44] Replace HTTP 422 to 400 in service classes status code handling --- src/main/java/org/pwss/service/FileService.java | 8 ++++---- .../pwss/service/MonitoredDirectoryService.java | 5 ++--- src/main/java/org/pwss/service/NoteService.java | 4 ++-- .../java/org/pwss/service/ScanSummaryService.java | 14 +++++++------- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/pwss/service/FileService.java b/src/main/java/org/pwss/service/FileService.java index 5c5be7b..d1a4314 100644 --- a/src/main/java/org/pwss/service/FileService.java +++ b/src/main/java/org/pwss/service/FileService.java @@ -57,7 +57,7 @@ public FileService() { * @return true if the file was successfully quarantined; false otherwise. * @throws QuarantineFileException If the file cannot be quarantined due to * various reasons such as - * unauthorized access, unprocessable entity, or + * unauthorized access, bad request, or * server errors. * @throws JsonProcessingException If there is an error during JSON * serialization or deserialization. @@ -84,8 +84,8 @@ public boolean quarantineFile(long fileId) throws QuarantineFileException, JsonP return switch (response.statusCode()) { case 200 -> parsed.map(QuarantineResponse::successful).orElse(false); + case 400 -> throw new QuarantineFileException("File cannot be quarantined: " + fileId); case 401 -> throw new QuarantineFileException("Unauthorized: Invalid credentials for file quarantine"); - case 422 -> throw new QuarantineFileException("File cannot be quarantined: " + fileId); case 500 -> throw new QuarantineFileException("Server error during file quarantine"); default -> throw new QuarantineFileException("File quarantine failed: Unexpected status code "); }; @@ -99,7 +99,7 @@ public boolean quarantineFile(long fileId) throws QuarantineFileException, JsonP * @return A `QuarantineResponse` object containing the response details. * @throws UnquarantineFileException If the file cannot be unquarantined due to * various reasons such as - * unauthorized access, unprocessable entity, + * unauthorized access, bad request, * or server errors. * @throws JsonProcessingException If there is an error during JSON * serialization or deserialization. @@ -125,8 +125,8 @@ public boolean unquarantineFile(QuarantineMetadata metadata) throws Unquarantine return switch (response.statusCode()) { case 200 -> true; + case 400 -> throw new UnquarantineFileException("File cannot be unquarantined: " + metadata.keyName()); case 401 -> throw new UnquarantineFileException("Unauthorized: Invalid credentials for file unquarantine"); - case 422 -> throw new UnquarantineFileException("File cannot be unquarantined: " + metadata.keyName()); case 500 -> throw new UnquarantineFileException("Server error during file unquarantine"); default -> throw new UnquarantineFileException("File unquarantine failed: Unexpected status code "); }; diff --git a/src/main/java/org/pwss/service/MonitoredDirectoryService.java b/src/main/java/org/pwss/service/MonitoredDirectoryService.java index 166c733..8477cf2 100644 --- a/src/main/java/org/pwss/service/MonitoredDirectoryService.java +++ b/src/main/java/org/pwss/service/MonitoredDirectoryService.java @@ -182,12 +182,11 @@ public boolean updateMonitoredDirectory(long id, boolean isActive, String notes, return switch (response.statusCode()) { case 200 -> true; + case 400 -> throw new UpdateMonitoredDirectoryException( + "Update monitored directory failed: invalid input data."); case 401 -> throw new UpdateMonitoredDirectoryException( "Update monitored directory failed: User not authorized to perform this action."); - case 422 -> - throw new UpdateMonitoredDirectoryException( - "Update monitored directory failed: Unprocessable entity - invalid input data."); case 500 -> throw new UpdateMonitoredDirectoryException( "Update monitored directory failed: An error occurred on the server while attempting to update the monitored directory."); diff --git a/src/main/java/org/pwss/service/NoteService.java b/src/main/java/org/pwss/service/NoteService.java index 1018a9e..07a4e1d 100644 --- a/src/main/java/org/pwss/service/NoteService.java +++ b/src/main/java/org/pwss/service/NoteService.java @@ -38,8 +38,8 @@ public boolean updateNotes(long noteId, String newNotes) throws UpdateNoteExcept HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.NOTE_UPDATE, body); return switch (response.statusCode()) { case 200 -> true; + case 400 -> throw new UpdateNoteException("Update notes failed: Invalid note ID or note content."); case 401 -> throw new UpdateNoteException("Update notes failed: User not authorized to perform this action."); - case 422 -> throw new UpdateNoteException("Update notes failed: Invalid note ID or note content."); case 500 -> throw new UpdateNoteException("Update notes failed: An error occurred on the server while attempting to update the notes."); default -> false; }; @@ -61,9 +61,9 @@ public boolean restoreNotes(long noteId, RestoreNoteType restoreNoteType) throws HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.NOTE_RESTORE, body); return switch (response.statusCode()) { case 200 -> true; + case 400 -> throw new RestoreNoteException("Restore notes failed: Invalid note ID or note content."); case 401 -> throw new RestoreNoteException("Restore notes failed: User not authorized to perform this action."); - case 422 -> throw new RestoreNoteException("Restore notes failed: Invalid note ID or note content."); case 500 -> throw new RestoreNoteException("Restore notes failed: An error occurred on the server while attempting to restore the notes."); default -> false; diff --git a/src/main/java/org/pwss/service/ScanSummaryService.java b/src/main/java/org/pwss/service/ScanSummaryService.java index bd883a7..ac99b5e 100644 --- a/src/main/java/org/pwss/service/ScanSummaryService.java +++ b/src/main/java/org/pwss/service/ScanSummaryService.java @@ -5,8 +5,8 @@ import java.net.http.HttpResponse; import java.util.List; import java.util.concurrent.ExecutionException; -import org.pwss.exception.scan_summary.GetMostRecentSummaryException; import org.pwss.exception.scan_summary.FileSearchException; +import org.pwss.exception.scan_summary.GetMostRecentSummaryException; import org.pwss.exception.scan_summary.GetSummaryForFileException; import org.pwss.exception.scan_summary.GetSummaryForScanException; import org.pwss.model.entity.File; @@ -67,12 +67,12 @@ public List getSummaryForFile(long fileId) throws GetSummaryForFile return switch (response.statusCode()) { case 200 -> List.of(objectMapper.readValue(response.body(), ScanSummary[].class)); + case 400 -> + throw new GetSummaryForFileException("Get summaries for file failed: The provided file ID is invalid."); case 401 -> throw new GetSummaryForFileException("Get summaries for file failed: User not authorized to perform this action."); case 404 -> throw new GetSummaryForFileException("Get summaries for file failed: No scan summaries found for the specified file."); - case 422 -> - throw new GetSummaryForFileException("Get summaries for file failed: The provided file ID is invalid."); case 500 -> throw new GetSummaryForFileException("Get summaries for file failed: An error occurred on the server while attempting to retrieve the scan summaries."); default -> null; @@ -96,11 +96,11 @@ public List searchFiles(String queryString, boolean ascending) throws File return switch (response.statusCode()) { case 200 -> List.of(objectMapper.readValue(response.body(), File[].class)); + case 400 -> + throw new FileSearchException("Search files failed: The provided search parameters are invalid."); case 401 -> throw new FileSearchException("Search files failed: User not authorized to perform this action."); case 404 -> List.of(); - case 422 -> - throw new FileSearchException("Search files failed: The provided search parameters are invalid."); case 500 -> throw new FileSearchException("Search files failed: An error occurred on the server while attempting to search for files."); default -> null; @@ -113,12 +113,12 @@ public List getScanSummaryForScan(long scanId) throws GetSummaryFor return switch (response.statusCode()) { case 200 -> List.of(objectMapper.readValue(response.body(), ScanSummary[].class)); + case 400 -> + throw new GetSummaryForScanException("Get summaries for scan failed: The provided scan ID is invalid."); case 401 -> throw new GetSummaryForScanException("Get summaries for scan failed: User not authorized to perform this action."); case 404 -> throw new GetSummaryForScanException("Get summaries for scan failed: No scan summaries found for the specified file."); - case 422 -> - throw new GetSummaryForScanException("Get summaries for scan failed: The provided scan ID is invalid."); case 500 -> throw new GetSummaryForScanException("Get summaries for scan failed: An error occurred on the server while attempting to retrieve the scan summaries."); default -> null; From 342a554ceabd55cdf6057fa139536901a49c33fc Mon Sep 17 00:00:00 2001 From: Stefan Date: Sun, 2 Nov 2025 19:44:26 +0100 Subject: [PATCH 06/44] Unlimited checkbox --- .../org/pwss/controller/HomeController.java | 68 ++++++++++-- .../java/org/pwss/service/ScanService.java | 12 +-- .../java/org/pwss/utils/ConversionUtils.java | 100 ++++++++++++++++++ .../java/org/pwss/view/screen/HomeScreen.form | 14 ++- .../java/org/pwss/view/screen/HomeScreen.java | 23 +++- src/main/resources/logback.xml | 2 +- 6 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/pwss/utils/ConversionUtils.java diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index ae645bb..487fa0a 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -13,7 +13,6 @@ import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicLong; - import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.JList; @@ -61,6 +60,7 @@ import org.pwss.service.ScanService; import org.pwss.service.ScanSummaryService; import org.pwss.utils.AppTheme; +import org.pwss.utils.ConversionUtils; import org.pwss.utils.LiveFeedUtils; import org.pwss.utils.MonitoredDirectoryUtils; import org.pwss.utils.OSUtils; @@ -71,7 +71,9 @@ import org.pwss.view.screen.HomeScreen; import org.slf4j.LoggerFactory; + import static org.pwss.app_settings.AppConfig.APP_THEME; +import static org.pwss.app_settings.AppConfig.MAX_HASH_EXTRACTION_FILE_SIZE; import static org.pwss.app_settings.AppConfig.USE_SPLASH_SCREEN; // TODO: NEEDS REFACTORING - VERY LARGE CLASS @@ -167,6 +169,11 @@ public final class HomeController extends BaseController { */ private boolean showSplashScreenSetting; + /** + * Maximum file size (in bytes) for which hash extraction will be performed. + */ + private long maxFileSizeForHashExtraction; + /** * Constructor to initialize HomeController with a HomeScreen view instance. * @@ -183,6 +190,7 @@ public HomeController(HomeScreen view) { this.monitoredDirectoryPopupFactory = new MonitoredDirectoryPopupFactory( new MonitoredDirectoryPopupListenerImpl(this, monitoredDirectoryService, noteService)); this.showSplashScreenSetting = USE_SPLASH_SCREEN; + this.maxFileSizeForHashExtraction = MAX_HASH_EXTRACTION_FILE_SIZE; } @Override @@ -412,10 +420,43 @@ public void mouseClicked(MouseEvent e) { } }); screen.getMaxHashExctractionFileSizeSlider().addChangeListener(l -> { - int value = screen.getMaxHashExctractionFileSizeSlider().getValue(); - log.debug("Setting max hash extraction file size to {} MB", value); - screen.getMaxHashExtractionFileSizeValueLabel().setText(value + " MB"); - // TODO: Update the AppConfig value + long valueMegabytes = screen.getMaxHashExctractionFileSizeSlider().getValue(); + log.debug("Setting max hash extraction file size to {} MB", valueMegabytes); + long valueBytes = ConversionUtils.megabytesToBytes(valueMegabytes); + + // Set App config value to size in bytes + if (AppConfig.setMaxHashExtractionFileSize(valueBytes)) { + screen.getMaxHashExtractionFileSizeValueLabel().setText(valueMegabytes + " MB"); + maxFileSizeForHashExtraction = ConversionUtils.megabytesToBytes(valueMegabytes); + } else { + log.error("Failed to update max hash extraction file size in app config."); + } + }); + screen.getMaxHashExctractionFileSizeUnlimitedCheckbox().addActionListener(l -> { + // If checked set the max size to -1L + boolean checked = screen.getMaxHashExctractionFileSizeUnlimitedCheckbox().isSelected(); + if (checked) { + log.debug("Setting max hash extraction file size to unlimited."); + if (AppConfig.setMaxHashExtractionFileSize(-1L)) { + maxFileSizeForHashExtraction = -1L; + screen.getMaxHashExtractionFileSizeValueLabel().setText("Unlimited"); + screen.getMaxHashExctractionFileSizeSlider().setEnabled(false); + } else { + log.error("Failed to update max hash extraction file size in app config."); + } + } else { + // If unchecked set the size to the current slider value + long sliderValueMegabytes = screen.getMaxHashExctractionFileSizeSlider().getValue(); + log.debug("Setting max hash extraction file size to {} MB", sliderValueMegabytes); + long sliderValueBytes = ConversionUtils.megabytesToBytes(sliderValueMegabytes); + if (AppConfig.setMaxHashExtractionFileSize(sliderValueBytes)) { + maxFileSizeForHashExtraction = sliderValueBytes; + screen.getMaxHashExtractionFileSizeValueLabel().setText(sliderValueMegabytes + " MB"); + screen.getMaxHashExctractionFileSizeSlider().setEnabled(true); + } else { + log.error("Failed to update max hash extraction file size in app config."); + } + } }); } @@ -556,6 +597,19 @@ public Component getListCellRendererComponent(JList list, Object value, int i } }); })); + + if (maxFileSizeForHashExtraction != -1L) { + screen.getMaxHashExctractionFileSizeUnlimitedCheckbox().setSelected(false); + screen.getMaxHashExctractionFileSizeSlider().setEnabled(true); + + final int maxSliderValueMegabytes = Math.toIntExact(ConversionUtils.bytesToMegabytes(maxFileSizeForHashExtraction)); + screen.getMaxHashExctractionFileSizeSlider().setValue(maxSliderValueMegabytes); + screen.getMaxHashExtractionFileSizeValueLabel().setText(maxSliderValueMegabytes + " MB"); + } else { + screen.getMaxHashExctractionFileSizeUnlimitedCheckbox().setSelected(true); + screen.getMaxHashExctractionFileSizeSlider().setEnabled(false); + screen.getMaxHashExtractionFileSizeValueLabel().setText("Unlimited"); + } } /** @@ -601,7 +655,7 @@ public void performStartScan(boolean singleDirectory) { int modelRow = table.convertRowIndexToModel(viewRow); Optional dir = model.getDirectoryAt(modelRow); if (dir.isPresent()) { - startScanSuccess = scanService.startScanById(dir.get().id()); + startScanSuccess = scanService.startScanById(dir.get().id(), maxFileSizeForHashExtraction); scanningDirs.add(dir.get()); baseLineScan = !dir.get().baselineEstablished(); } else { @@ -611,7 +665,7 @@ public void performStartScan(boolean singleDirectory) { } } else { baseLineScan = false; - startScanSuccess = scanService.startScan(); + startScanSuccess = scanService.startScan(maxFileSizeForHashExtraction); scanningDirs.addAll(allMonitoredDirectories); } SwingUtilities.invokeLater(() -> { diff --git a/src/main/java/org/pwss/service/ScanService.java b/src/main/java/org/pwss/service/ScanService.java index c297f41..2411a0d 100644 --- a/src/main/java/org/pwss/service/ScanService.java +++ b/src/main/java/org/pwss/service/ScanService.java @@ -41,15 +41,13 @@ public ScanService() { /** * Starts a scan by sending a request to the START_SCAN endpoint. * + * @param maxHashExtractionFileSize The maximum file size for hash extraction. * @return `true` if the scan start request is successful, otherwise `false`. * @throws StartFullScanException If the scan start attempt fails due to various reasons such as invalid credentials, no active monitored directories, scan already running, or server error. * @throws ExecutionException If an error occurs during the asynchronous execution of the request. * @throws InterruptedException If the thread executing the request is interrupted. */ - public boolean startScan() throws StartFullScanException, ExecutionException, InterruptedException, JsonProcessingException { - // Read max hash extraction file size from config - long maxHashExtractionFileSize = AppConfig.MAX_HASH_EXTRACTION_FILE_SIZE; - + public boolean startScan(long maxHashExtractionFileSize) throws StartFullScanException, ExecutionException, InterruptedException, JsonProcessingException { String body = objectMapper.writeValueAsString(new StartScanAllRequest(maxHashExtractionFileSize)); HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.START_SCAN, body); @@ -70,16 +68,14 @@ public boolean startScan() throws StartFullScanException, ExecutionException, In * Starts a scan for a specific monitored directory by its ID by sending a request to the START_SCAN_ID endpoint. * * @param id The ID of the monitored directory to start the scan for. + * @param maxHashExtractionFileSize The maximum file size for hash extraction. * @return `true` if the scan start request is successful, otherwise `false`. * @throws StartScanByIdException If the scan start attempt fails due to various reasons such as invalid credentials, monitored directory not found, monitored directory inactive, scan already running, or server error. * @throws ExecutionException If an error occurs during the asynchronous execution of the request. * @throws InterruptedException If the thread executing the request is interrupted. * @throws JsonProcessingException If an error occurs while serializing the start scan request to JSON. */ - public boolean startScanById(long id) throws StartScanByIdException, ExecutionException, InterruptedException, JsonProcessingException { - // Read max hash extraction file size from config - long maxHashExtractionFileSize = AppConfig.MAX_HASH_EXTRACTION_FILE_SIZE; - + public boolean startScanById(long id, long maxHashExtractionFileSize) throws StartScanByIdException, ExecutionException, InterruptedException, JsonProcessingException { String body = objectMapper.writeValueAsString(new StartSingleScanRequest(id, maxHashExtractionFileSize)); HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.START_SCAN_ID, body); diff --git a/src/main/java/org/pwss/utils/ConversionUtils.java b/src/main/java/org/pwss/utils/ConversionUtils.java new file mode 100644 index 0000000..3c1e1a4 --- /dev/null +++ b/src/main/java/org/pwss/utils/ConversionUtils.java @@ -0,0 +1,100 @@ +package org.pwss.utils; + +/** + * Utility class providing methods to convert between different units of data + * measurement, such as bytes, + * megabytes, and gigabytes. This class includes static utility methods that + * perform these conversions. + * + *

+ * The primary purpose of this class is to facilitate unit conversions in + * scenarios where file sizes or + * other data measurements need to be handled in a specific unit for easier + * comprehension or processing. + *

+ */ +public class ConversionUtils { + private static final long MEGABYTE = 1024L * 1024L; + private static final long GIGABYTE = MEGABYTE * 1024L; + + /** + * Converts a value in bytes to megabytes. + * + * @param bytes the number of bytes to convert + * @return the equivalent value in megabytes as a Long + */ + public static Long bytesToMegabytes(Long bytes) { + if (bytes == null) { + throw new IllegalArgumentException("Bytes value cannot be null."); + } + return bytes / MEGABYTE; + } + + /** + * Converts a value in megabytes to bytes. + * + * @param megabytes the number of megabytes to convert + * @return the equivalent value in bytes as a Long + */ + public static Long megabytesToBytes(Long megabytes) { + if (megabytes == null) { + throw new IllegalArgumentException("Megabytes value cannot be null."); + } + return megabytes * MEGABYTE; + } + + // Optional: If you want to handle conversion with double values for more + // precise calculations + /** + * Converts a value in bytes to megabytes. + * + * @param bytes the number of bytes to convert + * @return the equivalent value in megabytes as a Double + */ + public static Double bytesToMegabytesDouble(Long bytes) { + if (bytes == null) { + throw new IllegalArgumentException("Bytes value cannot be null."); + } + return (double) bytes / MEGABYTE; + } + + /** + * Converts a value in megabytes to bytes. + * + * @param megabytes the number of megabytes to convert + * @return the equivalent value in bytes as a Double + */ + public static Long megabytesToBytesDouble(Double megabytes) { + if (megabytes == null) { + throw new IllegalArgumentException("Megabytes value cannot be null."); + } + return Math.round(megabytes * MEGABYTE); + } + + /** + * Converts a value in bytes to gigabytes. + * + * @param bytes the number of bytes to convert + * @return the equivalent value in gigabytes as a Double + */ + public static Double bytesToGigabytesDouble(Long bytes) { + if (bytes == null) { + throw new IllegalArgumentException("Bytes value cannot be null."); + } + return (double) bytes / GIGABYTE; + } + + /** + * Converts a value in gigabytes to bytes. + * + * @param gigabytes the number of gigabytes to convert + * @return the equivalent value in bytes as a Double + */ + public static Long gigabytesToBytesDouble(Double gigabytes) { + if (gigabytes == null) { + throw new IllegalArgumentException("Gigabytes value cannot be null."); + } + return Math.round(gigabytes * GIGABYTE); + } + +} \ No newline at end of file diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.form b/src/main/java/org/pwss/view/screen/HomeScreen.form index eba3f55..d81e88d 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.form +++ b/src/main/java/org/pwss/view/screen/HomeScreen.form @@ -495,7 +495,7 @@
- + @@ -516,9 +516,9 @@ - + - + @@ -548,6 +548,14 @@ + + + + + + + + diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.java b/src/main/java/org/pwss/view/screen/HomeScreen.java index a10d94d..20840c0 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.java +++ b/src/main/java/org/pwss/view/screen/HomeScreen.java @@ -223,6 +223,11 @@ public class HomeScreen extends BaseScreen { */ private JLabel maxHashExtractionFileSizeValueLabel; + /** + * Checkbox to set the maximum hash extraction file size to unlimited. + */ + private JCheckBox maxHashExctractionFileSizeUnlimitedCheckbox; + @Override protected String getScreenName() { return "Home"; @@ -494,6 +499,15 @@ public JSlider getMaxHashExctractionFileSizeSlider() { return maxHashExctractionFileSizeSlider; } + /** + * Returns the checkbox to set the maximum hash extraction file size to unlimited. + * + * @return JCheckBox representing the "Unlimited" option for max hash extraction file size. + */ + public JCheckBox getMaxHashExctractionFileSizeUnlimitedCheckbox() { + return maxHashExctractionFileSizeUnlimitedCheckbox; + } + { // GUI initializer generated by IntelliJ IDEA GUI Designer // >>> IMPORTANT!! <<< @@ -687,15 +701,15 @@ public JSlider getMaxHashExctractionFileSizeSlider() { label8.setText("© 2025 PWSS ORG"); panel5.add(label8, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JPanel panel6 = new JPanel(); - panel6.setLayout(new GridLayoutManager(5, 1, new Insets(0, 0, 0, 0), -1, -1)); + panel6.setLayout(new GridLayoutManager(6, 1, new Insets(0, 0, 0, 0), -1, -1)); panel5.add(panel6, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); showSplashScreenCheckBox = new JCheckBox(); showSplashScreenCheckBox.setText("Show splash screen"); panel6.add(showSplashScreenCheckBox, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); maxHashExctractionFileSizeSlider = new JSlider(); - maxHashExctractionFileSizeSlider.setMajorTickSpacing(4096); + maxHashExctractionFileSizeSlider.setMajorTickSpacing(5120); maxHashExctractionFileSizeSlider.setMaximum(102400); - maxHashExctractionFileSizeSlider.setMinimum(2048); + maxHashExctractionFileSizeSlider.setMinimum(100); maxHashExctractionFileSizeSlider.setMinorTickSpacing(1024); maxHashExctractionFileSizeSlider.setPaintLabels(false); maxHashExctractionFileSizeSlider.setPaintTicks(true); @@ -710,6 +724,9 @@ public JSlider getMaxHashExctractionFileSizeSlider() { maxHashExtractionFileSizeValueLabel = new JLabel(); maxHashExtractionFileSizeValueLabel.setText("Selected:"); panel6.add(maxHashExtractionFileSizeValueLabel, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + maxHashExctractionFileSizeUnlimitedCheckbox = new JCheckBox(); + maxHashExctractionFileSizeUnlimitedCheckbox.setText("Unlimited"); + panel6.add(maxHashExctractionFileSizeUnlimitedCheckbox, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label10 = new JLabel(); label10.setText("Theme"); panel5.add(label10, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 83566ff..bf24e35 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -11,7 +11,7 @@ - + \ No newline at end of file From fca5da15b3181f947d2369cb1807e9f7552dc182 Mon Sep 17 00:00:00 2001 From: Stefan Date: Sun, 2 Nov 2025 19:55:33 +0100 Subject: [PATCH 07/44] Add safeguard for not allowing /dev & /proc to be set as monitored directories on unix based systems --- .../controller/NewDirectoryController.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/pwss/controller/NewDirectoryController.java b/src/main/java/org/pwss/controller/NewDirectoryController.java index ad5da69..35f3c74 100644 --- a/src/main/java/org/pwss/controller/NewDirectoryController.java +++ b/src/main/java/org/pwss/controller/NewDirectoryController.java @@ -5,6 +5,7 @@ import org.pwss.navigation.NavigationEvents; import org.pwss.navigation.Screen; import org.pwss.service.MonitoredDirectoryService; +import org.pwss.utils.OSUtils; import org.pwss.utils.StringConstants; import org.pwss.view.screen.NewDirectoryScreen; @@ -42,16 +43,23 @@ public void onShow() { @Override void initListeners() { - getScreen().getSelectPathButton().addActionListener(e -> openFolderPicker()); - getScreen().getCancelButton().addActionListener(e -> NavigationEvents.navigateTo(Screen.HOME)); - getScreen().getCreateButton().addActionListener(e -> createNewDirectory()); + screen.getSelectPathButton().addActionListener(e -> openFolderPicker()); + screen.getCancelButton().addActionListener(e -> NavigationEvents.navigateTo(Screen.HOME)); + screen.getCreateButton().addActionListener(e -> createNewDirectory()); } @Override void refreshView() { - getScreen().getPathLabel() - .setText(selectedPath != null ? selectedPath : StringConstants.NEW_DIR_NO_PATH_SELECTED); - getScreen().getCreateButton().setEnabled(selectedPath != null && !selectedPath.isEmpty()); + screen.getPathLabel().setText(selectedPath != null ? selectedPath : StringConstants.NEW_DIR_NO_PATH_SELECTED); + screen.getCreateButton().setEnabled(selectedPath != null && !selectedPath.isEmpty()); + + // Additional check for Unix-based systems to disable creation for /dev and /proc paths + if (OSUtils.isUnix() && selectedPath != null) { + if (selectedPath.startsWith("/dev") || selectedPath.startsWith("/proc")) { + screen.getCreateButton().setEnabled(false); + screen.showError("Cannot monitor directories under /dev or /proc on Unix-based systems."); + } + } } /** @@ -74,16 +82,16 @@ private void openFolderPicker() { * Navigates back to the home screen upon successful creation. */ private void createNewDirectory() { - boolean includeSubdirectories = getScreen().getIncludeSubdirectoriesCheckBox().isSelected(); - boolean makeActive = getScreen().getMakeDirectoryActiveCheckBox().isSelected(); + boolean includeSubdirectories = screen.getIncludeSubdirectoriesCheckBox().isSelected(); + boolean makeActive = screen.getMakeDirectoryActiveCheckBox().isSelected(); if (selectedPath != null && !selectedPath.isEmpty()) { try { monitoredDirectoryService.createNewMonitoredDirectory(selectedPath, includeSubdirectories, makeActive); - JOptionPane.showMessageDialog(getScreen().getRootPanel(), StringConstants.NEW_DIR_SUCCESS_TEXT, + JOptionPane.showMessageDialog(screen.getRootPanel(), StringConstants.NEW_DIR_SUCCESS_TEXT, StringConstants.GENERIC_SUCCESS, JOptionPane.INFORMATION_MESSAGE); NavigationEvents.navigateTo(Screen.HOME); } catch (Exception e) { - JOptionPane.showMessageDialog(getScreen().getRootPanel(), + JOptionPane.showMessageDialog(screen.getRootPanel(), StringConstants.NEW_DIR_ERROR_PREFIX + e.getMessage(), StringConstants.GENERIC_ERROR, JOptionPane.ERROR_MESSAGE); } From 8064b72dfb5350ceaa1111c0ccd834d567a96a98 Mon Sep 17 00:00:00 2001 From: Stefan Date: Sun, 2 Nov 2025 19:56:32 +0100 Subject: [PATCH 08/44] Bump to 1.1 :) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 432db6f..7f23fdd 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.pwss integrity_hash jar - 1.0 + 1.1 A GUI application for file integrity checking From 34fee1dbd334a9a02c7fcd111764ad72d195f82c Mon Sep 17 00:00:00 2001 From: Peter Westin <83552499+pwgit-create@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:27:52 +0100 Subject: [PATCH 09/44] Update src/main/java/org/pwss/view/screen/HomeScreen.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/org/pwss/view/screen/HomeScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.java b/src/main/java/org/pwss/view/screen/HomeScreen.java index 20840c0..b9daa59 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.java +++ b/src/main/java/org/pwss/view/screen/HomeScreen.java @@ -226,7 +226,7 @@ public class HomeScreen extends BaseScreen { /** * Checkbox to set the maximum hash extraction file size to unlimited. */ - private JCheckBox maxHashExctractionFileSizeUnlimitedCheckbox; + private JCheckBox maxHashExtractionFileSizeUnlimitedCheckbox; @Override protected String getScreenName() { From cbb8316496daf7be5234f103663b9da2dbaf27a9 Mon Sep 17 00:00:00 2001 From: Peter Westin <83552499+pwgit-create@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:28:23 +0100 Subject: [PATCH 10/44] Update src/main/java/org/pwss/view/screen/HomeScreen.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../java/org/pwss/view/screen/HomeScreen.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.java b/src/main/java/org/pwss/view/screen/HomeScreen.java index b9daa59..4430523 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.java +++ b/src/main/java/org/pwss/view/screen/HomeScreen.java @@ -706,15 +706,15 @@ public JCheckBox getMaxHashExctractionFileSizeUnlimitedCheckbox() { showSplashScreenCheckBox = new JCheckBox(); showSplashScreenCheckBox.setText("Show splash screen"); panel6.add(showSplashScreenCheckBox, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - maxHashExctractionFileSizeSlider = new JSlider(); - maxHashExctractionFileSizeSlider.setMajorTickSpacing(5120); - maxHashExctractionFileSizeSlider.setMaximum(102400); - maxHashExctractionFileSizeSlider.setMinimum(100); - maxHashExctractionFileSizeSlider.setMinorTickSpacing(1024); - maxHashExctractionFileSizeSlider.setPaintLabels(false); - maxHashExctractionFileSizeSlider.setPaintTicks(true); - maxHashExctractionFileSizeSlider.setPaintTrack(true); - maxHashExctractionFileSizeSlider.setSnapToTicks(true); + maxHashExtractionFileSizeSlider = new JSlider(); + maxHashExtractionFileSizeSlider.setMajorTickSpacing(5120); + maxHashExtractionFileSizeSlider.setMaximum(102400); + maxHashExtractionFileSizeSlider.setMinimum(100); + maxHashExtractionFileSizeSlider.setMinorTickSpacing(1024); + maxHashExtractionFileSizeSlider.setPaintLabels(false); + maxHashExtractionFileSizeSlider.setPaintTicks(true); + maxHashExtractionFileSizeSlider.setPaintTrack(true); + maxHashExtractionFileSizeSlider.setSnapToTicks(true); panel6.add(maxHashExctractionFileSizeSlider, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label9 = new JLabel(); label9.setText("Max hash exctraction file size (MB)"); From ed4c6acd010adb16d2ee5a85228da8edf5f29008 Mon Sep 17 00:00:00 2001 From: Peter Westin <83552499+pwgit-create@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:28:40 +0100 Subject: [PATCH 11/44] Update src/main/java/org/pwss/controller/HomeController.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/org/pwss/controller/HomeController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index 487fa0a..075edcd 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -419,8 +419,8 @@ public void mouseClicked(MouseEvent e) { } }); - screen.getMaxHashExctractionFileSizeSlider().addChangeListener(l -> { - long valueMegabytes = screen.getMaxHashExctractionFileSizeSlider().getValue(); + screen.getMaxHashExtractionFileSizeSlider().addChangeListener(l -> { + long valueMegabytes = screen.getMaxHashExtractionFileSizeSlider().getValue(); log.debug("Setting max hash extraction file size to {} MB", valueMegabytes); long valueBytes = ConversionUtils.megabytesToBytes(valueMegabytes); From 43858eb28eda75d2a1a9d1116a2d46962d3da5bf Mon Sep 17 00:00:00 2001 From: Peter Westin <83552499+pwgit-create@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:28:56 +0100 Subject: [PATCH 12/44] Update src/main/java/org/pwss/controller/HomeController.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/org/pwss/controller/HomeController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index 075edcd..f259985 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -599,15 +599,15 @@ public Component getListCellRendererComponent(JList list, Object value, int i })); if (maxFileSizeForHashExtraction != -1L) { - screen.getMaxHashExctractionFileSizeUnlimitedCheckbox().setSelected(false); - screen.getMaxHashExctractionFileSizeSlider().setEnabled(true); + screen.getMaxHashExtractionFileSizeUnlimitedCheckbox().setSelected(false); + screen.getMaxHashExtractionFileSizeSlider().setEnabled(true); final int maxSliderValueMegabytes = Math.toIntExact(ConversionUtils.bytesToMegabytes(maxFileSizeForHashExtraction)); - screen.getMaxHashExctractionFileSizeSlider().setValue(maxSliderValueMegabytes); + screen.getMaxHashExtractionFileSizeSlider().setValue(maxSliderValueMegabytes); screen.getMaxHashExtractionFileSizeValueLabel().setText(maxSliderValueMegabytes + " MB"); } else { - screen.getMaxHashExctractionFileSizeUnlimitedCheckbox().setSelected(true); - screen.getMaxHashExctractionFileSizeSlider().setEnabled(false); + screen.getMaxHashExtractionFileSizeUnlimitedCheckbox().setSelected(true); + screen.getMaxHashExtractionFileSizeSlider().setEnabled(false); screen.getMaxHashExtractionFileSizeValueLabel().setText("Unlimited"); } } From bcfed1a4e25fc5179e68d637f7598371b5903aec Mon Sep 17 00:00:00 2001 From: Peter Westin <83552499+pwgit-create@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:29:05 +0100 Subject: [PATCH 13/44] Update src/main/java/org/pwss/app_settings/ConfigLoader.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/org/pwss/app_settings/ConfigLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/pwss/app_settings/ConfigLoader.java b/src/main/java/org/pwss/app_settings/ConfigLoader.java index f197389..859fb9d 100644 --- a/src/main/java/org/pwss/app_settings/ConfigLoader.java +++ b/src/main/java/org/pwss/app_settings/ConfigLoader.java @@ -326,7 +326,7 @@ final boolean setMaxHashExtractionFileSize(String maxHashExtractionFileSize) { return true; } catch (Exception exception) { log.debug("Max hash extraction file size could not be set in app.config file", exception); - log.error("Max hash extraction file size could not be set in app.config file", exception.getMessage()); + log.error("Max hash extraction file size could not be set in app.config file: {}", exception.getMessage()); return false; } } From 62e3e05dfc159b1157a5ded4adaabde1ab64bcda Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Sun, 2 Nov 2025 20:51:54 +0100 Subject: [PATCH 14/44] Spelling error correction --- .../org/pwss/controller/HomeController.java | 10 ++++---- .../java/org/pwss/view/screen/HomeScreen.java | 25 +++++++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index f259985..645209c 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -432,27 +432,27 @@ public void mouseClicked(MouseEvent e) { log.error("Failed to update max hash extraction file size in app config."); } }); - screen.getMaxHashExctractionFileSizeUnlimitedCheckbox().addActionListener(l -> { + screen.getMaxHashExtractionFileSizeUnlimitedCheckbox().addActionListener(l -> { // If checked set the max size to -1L - boolean checked = screen.getMaxHashExctractionFileSizeUnlimitedCheckbox().isSelected(); + boolean checked = screen.getMaxHashExtractionFileSizeUnlimitedCheckbox().isSelected(); if (checked) { log.debug("Setting max hash extraction file size to unlimited."); if (AppConfig.setMaxHashExtractionFileSize(-1L)) { maxFileSizeForHashExtraction = -1L; screen.getMaxHashExtractionFileSizeValueLabel().setText("Unlimited"); - screen.getMaxHashExctractionFileSizeSlider().setEnabled(false); + screen.getMaxHashExtractionFileSizeSlider().setEnabled(false); } else { log.error("Failed to update max hash extraction file size in app config."); } } else { // If unchecked set the size to the current slider value - long sliderValueMegabytes = screen.getMaxHashExctractionFileSizeSlider().getValue(); + long sliderValueMegabytes = screen.getMaxHashExtractionFileSizeSlider().getValue(); log.debug("Setting max hash extraction file size to {} MB", sliderValueMegabytes); long sliderValueBytes = ConversionUtils.megabytesToBytes(sliderValueMegabytes); if (AppConfig.setMaxHashExtractionFileSize(sliderValueBytes)) { maxFileSizeForHashExtraction = sliderValueBytes; screen.getMaxHashExtractionFileSizeValueLabel().setText(sliderValueMegabytes + " MB"); - screen.getMaxHashExctractionFileSizeSlider().setEnabled(true); + screen.getMaxHashExtractionFileSizeSlider().setEnabled(true); } else { log.error("Failed to update max hash extraction file size in app config."); } diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.java b/src/main/java/org/pwss/view/screen/HomeScreen.java index 4430523..4c93b9c 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.java +++ b/src/main/java/org/pwss/view/screen/HomeScreen.java @@ -4,6 +4,9 @@ import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.core.GridLayoutManager; import com.intellij.uiDesigner.core.Spacer; + +import static org.pwss.app_settings.AppConfig.APP_THEME; + import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; @@ -216,7 +219,7 @@ public class HomeScreen extends BaseScreen { /** * Slider for setting the maximum file size for hash extraction. */ - private JSlider maxHashExctractionFileSizeSlider; + private JSlider maxHashExtractionFileSizeSlider; /** * Label displaying the value of the maximum file size for hash extraction. @@ -495,8 +498,8 @@ public JLabel getMaxHashExtractionFileSizeValueLabel() { * * @return JSlider for maximum hash extraction file size. */ - public JSlider getMaxHashExctractionFileSizeSlider() { - return maxHashExctractionFileSizeSlider; + public JSlider getMaxHashExtractionFileSizeSlider() { + return maxHashExtractionFileSizeSlider; } /** @@ -504,8 +507,8 @@ public JSlider getMaxHashExctractionFileSizeSlider() { * * @return JCheckBox representing the "Unlimited" option for max hash extraction file size. */ - public JCheckBox getMaxHashExctractionFileSizeUnlimitedCheckbox() { - return maxHashExctractionFileSizeUnlimitedCheckbox; + public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { + return maxHashExtractionFileSizeUnlimitedCheckbox; } { @@ -564,7 +567,7 @@ public JCheckBox getMaxHashExctractionFileSizeUnlimitedCheckbox() { panel2.add(label2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JScrollPane scrollPane2 = new JScrollPane(); panel2.add(scrollPane2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); - monitoredDirectoryList = new JList(); + monitoredDirectoryList = new JList(); monitoredDirectoryList.setEnabled(true); monitoredDirectoryList.setSelectionMode(0); monitoredDirectoryList.setToolTipText("Monitored directories list"); @@ -692,7 +695,7 @@ public JCheckBox getMaxHashExctractionFileSizeUnlimitedCheckbox() { panel5.add(label7, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final Spacer spacer1 = new Spacer(); panel5.add(spacer1, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); - themePicker = new JComboBox(); + themePicker = new JComboBox(); panel5.add(themePicker, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); restartButton = new JButton(); restartButton.setText("Restart"); @@ -715,7 +718,7 @@ public JCheckBox getMaxHashExctractionFileSizeUnlimitedCheckbox() { maxHashExtractionFileSizeSlider.setPaintTicks(true); maxHashExtractionFileSizeSlider.setPaintTrack(true); maxHashExtractionFileSizeSlider.setSnapToTicks(true); - panel6.add(maxHashExctractionFileSizeSlider, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel6.add(maxHashExtractionFileSizeSlider, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label9 = new JLabel(); label9.setText("Max hash exctraction file size (MB)"); panel6.add(label9, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); @@ -724,9 +727,9 @@ public JCheckBox getMaxHashExctractionFileSizeUnlimitedCheckbox() { maxHashExtractionFileSizeValueLabel = new JLabel(); maxHashExtractionFileSizeValueLabel.setText("Selected:"); panel6.add(maxHashExtractionFileSizeValueLabel, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - maxHashExctractionFileSizeUnlimitedCheckbox = new JCheckBox(); - maxHashExctractionFileSizeUnlimitedCheckbox.setText("Unlimited"); - panel6.add(maxHashExctractionFileSizeUnlimitedCheckbox, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + maxHashExtractionFileSizeUnlimitedCheckbox = new JCheckBox(); + maxHashExtractionFileSizeUnlimitedCheckbox.setText("Unlimited"); + panel6.add(maxHashExtractionFileSizeUnlimitedCheckbox, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label10 = new JLabel(); label10.setText("Theme"); panel5.add(label10, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); From 32c9260265ebae0a528e17a26af1349e1e58a304 Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Sun, 2 Nov 2025 20:56:54 +0100 Subject: [PATCH 15/44] Corrected grammar error no.2 --- .../java/org/pwss/view/screen/HomeScreen.form | 6 +- .../java/org/pwss/view/screen/HomeScreen.java | 286 +++++++++++++----- 2 files changed, 220 insertions(+), 72 deletions(-) diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.form b/src/main/java/org/pwss/view/screen/HomeScreen.form index d81e88d..9c6ae80 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.form +++ b/src/main/java/org/pwss/view/screen/HomeScreen.form @@ -511,7 +511,7 @@ - + @@ -531,7 +531,7 @@
- + @@ -548,7 +548,7 @@ - + diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.java b/src/main/java/org/pwss/view/screen/HomeScreen.java index 4c93b9c..bda50d4 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.java +++ b/src/main/java/org/pwss/view/screen/HomeScreen.java @@ -1,12 +1,9 @@ package org.pwss.view.screen; - import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.core.GridLayoutManager; import com.intellij.uiDesigner.core.Spacer; -import static org.pwss.app_settings.AppConfig.APP_THEME; - import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; @@ -33,7 +30,6 @@ import org.pwss.model.entity.MonitoredDirectory; import org.pwss.utils.AppTheme; - /** * The HomeScreen class represents the main screen of the application. * It extends BaseScreen and contains various UI components that make up @@ -47,7 +43,8 @@ public class HomeScreen extends BaseScreen { private JPanel rootPanel; /** - * A tabbed pane that allows switching between different tabs in the home screen. + * A tabbed pane that allows switching between different tabs in the home + * screen. */ private JTabbedPane tabbedPane; @@ -503,18 +500,20 @@ public JSlider getMaxHashExtractionFileSizeSlider() { } /** - * Returns the checkbox to set the maximum hash extraction file size to unlimited. + * Returns the checkbox to set the maximum hash extraction file size to + * unlimited. * - * @return JCheckBox representing the "Unlimited" option for max hash extraction file size. + * @return JCheckBox representing the "Unlimited" option for max hash extraction + * file size. */ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { return maxHashExtractionFileSizeUnlimitedCheckbox; } { -// GUI initializer generated by IntelliJ IDEA GUI Designer -// >>> IMPORTANT!! <<< -// DO NOT EDIT OR ADD ANY CODE HERE! + // GUI initializer generated by IntelliJ IDEA GUI Designer + // >>> IMPORTANT!! <<< + // DO NOT EDIT OR ADD ANY CODE HERE! $$$setupUI$$$(); } @@ -530,29 +529,49 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { rootPanel.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); tabbedPane = new JTabbedPane(); Font tabbedPaneFont = this.$$$getFont$$$(null, -1, 14, tabbedPane.getFont()); - if (tabbedPaneFont != null) tabbedPane.setFont(tabbedPaneFont); + if (tabbedPaneFont != null) + tabbedPane.setFont(tabbedPaneFont); tabbedPane.setTabPlacement(1); tabbedPane.setToolTipText(""); - rootPanel.add(tabbedPane, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); + rootPanel.add(tabbedPane, + new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_FIXED, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, + 0, false)); homeTab = new JPanel(); homeTab.setLayout(new GridLayoutManager(4, 1, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane.addTab("\uD83C\uDFE0 Home", homeTab); newDirectoryButton = new JButton(); newDirectoryButton.setText("New directory"); - homeTab.add(newDirectoryButton, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + homeTab.add(newDirectoryButton, + new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JSplitPane splitPane1 = new JSplitPane(); - homeTab.add(splitPane1, new GridConstraints(1, 0, 2, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); + homeTab.add(splitPane1, + new GridConstraints(1, 0, 2, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, + new Dimension(200, 200), null, 0, false)); final JPanel panel1 = new JPanel(); panel1.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); splitPane1.setLeftComponent(panel1); final JLabel label1 = new JLabel(); Font label1Font = this.$$$getFont$$$(null, Font.BOLD, 16, label1.getFont()); - if (label1Font != null) label1.setFont(label1Font); + if (label1Font != null) + label1.setFont(label1Font); label1.setText("Most recent scans"); - panel1.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(label1, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JScrollPane scrollPane1 = new JScrollPane(); scrollPane1.setToolTipText("Double click a scan to see details"); - panel1.add(scrollPane1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); + panel1.add(scrollPane1, + new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, + 0, false)); recentScanTable = new JTable(); recentScanTable.setAutoResizeMode(2); recentScanTable.setShowHorizontalLines(true); @@ -562,11 +581,17 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { splitPane1.setRightComponent(panel2); final JLabel label2 = new JLabel(); Font label2Font = this.$$$getFont$$$(null, Font.BOLD, 16, label2.getFont()); - if (label2Font != null) label2.setFont(label2Font); + if (label2Font != null) + label2.setFont(label2Font); label2.setText("Monitored directories"); - panel2.add(label2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel2.add(label2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JScrollPane scrollPane2 = new JScrollPane(); - panel2.add(scrollPane2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); + panel2.add(scrollPane2, + new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, + 0, false)); monitoredDirectoryList = new JList(); monitoredDirectoryList.setEnabled(true); monitoredDirectoryList.setSelectionMode(0); @@ -574,56 +599,96 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { scrollPane2.setViewportView(monitoredDirectoryList); notificationPanel = new JPanel(); notificationPanel.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 10, 0), -1, -1)); - homeTab.add(notificationPanel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); + homeTab.add(notificationPanel, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, + 0, true)); final JLabel label3 = new JLabel(); Font label3Font = this.$$$getFont$$$(null, Font.BOLD, 16, label3.getFont()); - if (label3Font != null) label3.setFont(label3Font); + if (label3Font != null) + label3.setFont(label3Font); label3.setText("Notifications"); - notificationPanel.add(label3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + notificationPanel.add(label3, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); notificationTextArea = new JTextArea(); notificationTextArea.setEditable(false); - notificationPanel.add(notificationTextArea, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(150, 50), null, 0, false)); + notificationPanel.add(notificationTextArea, + new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, + new Dimension(150, 50), null, 0, false)); scanTab = new JPanel(); scanTab.setLayout(new GridLayoutManager(10, 4, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane.addTab("\uD83D\uDD0E Scan", scanTab); final JLabel label4 = new JLabel(); Font label4Font = this.$$$getFont$$$(null, Font.BOLD, 16, label4.getFont()); - if (label4Font != null) label4.setFont(label4Font); + if (label4Font != null) + label4.setFont(label4Font); label4.setHorizontalAlignment(0); label4.setText("Monitored directories"); - scanTab.add(label4, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanTab.add(label4, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JScrollPane scrollPane3 = new JScrollPane(); scrollPane3.setToolTipText("Right click a monitored directory for different actions"); - scanTab.add(scrollPane3, new GridConstraints(1, 0, 5, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); + scanTab.add(scrollPane3, + new GridConstraints(1, 0, 5, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, + 0, false)); monitoredDirectoriesTable = new JTable(); monitoredDirectoriesTable.setAutoResizeMode(2); scrollPane3.setViewportView(monitoredDirectoriesTable); liveFeedContainer = new JScrollPane(); - scanTab.add(liveFeedContainer, new GridConstraints(1, 1, 8, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, new Dimension(450, -1), new Dimension(450, -1), null, 0, false)); + scanTab.add(liveFeedContainer, + new GridConstraints(1, 1, 8, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + new Dimension(450, -1), new Dimension(450, -1), null, 0, false)); liveFeedText = new JTextPane(); liveFeedText.setEditable(false); liveFeedText.setText(""); liveFeedContainer.setViewportView(liveFeedText); liveFeedTitle = new JLabel(); Font liveFeedTitleFont = this.$$$getFont$$$(null, Font.BOLD, 16, liveFeedTitle.getFont()); - if (liveFeedTitleFont != null) liveFeedTitle.setFont(liveFeedTitleFont); + if (liveFeedTitleFont != null) + liveFeedTitle.setFont(liveFeedTitleFont); liveFeedTitle.setHorizontalAlignment(0); liveFeedTitle.setText("Scan logs"); - scanTab.add(liveFeedTitle, new GridConstraints(0, 1, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanTab.add(liveFeedTitle, + new GridConstraints(0, 1, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); liveFeedDiffCount = new JLabel(); liveFeedDiffCount.setText(""); - scanTab.add(liveFeedDiffCount, new GridConstraints(9, 2, 1, 2, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanTab.add(liveFeedDiffCount, + new GridConstraints(9, 2, 1, 2, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); scanButton = new JButton(); scanButton.setText("Full scan"); - scanTab.add(scanButton, new GridConstraints(6, 0, 4, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanTab.add(scanButton, + new GridConstraints(6, 0, 4, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); clearFeedButton = new JButton(); clearFeedButton.setText("Clear feed"); - scanTab.add(clearFeedButton, new GridConstraints(0, 3, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanTab.add(clearFeedButton, + new GridConstraints(0, 3, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); filesTab = new JPanel(); filesTab.setLayout(new GridLayoutManager(2, 1, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane.addTab("\uD83D\uDDC4\uFE0F Files", filesTab); final JSplitPane splitPane2 = new JSplitPane(); - filesTab.add(splitPane2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); + filesTab.add(splitPane2, + new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, + new Dimension(200, 200), null, 0, false)); final JScrollPane scrollPane4 = new JScrollPane(); splitPane2.setLeftComponent(scrollPane4); filesTable = new JTable(); @@ -633,7 +698,11 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { splitPane2.setRightComponent(panel3); final JSplitPane splitPane3 = new JSplitPane(); splitPane3.setOrientation(0); - panel3.add(splitPane3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); + panel3.add(splitPane3, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, + new Dimension(200, 200), null, 0, false)); final JScrollPane scrollPane5 = new JScrollPane(); splitPane3.setLeftComponent(scrollPane5); fileSummaryTable = new JTable(); @@ -645,26 +714,47 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { scrollPane6.setViewportView(scanSummaryDetails); final JPanel panel4 = new JPanel(); panel4.setLayout(new GridLayoutManager(5, 2, new Insets(0, 0, 0, 0), -1, -1)); - filesTab.add(panel4, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_NORTH, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); + filesTab.add(panel4, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_NORTH, GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, + 0, true)); fileSearchField = new JTextField(); - panel4.add(fileSearchField, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); + panel4.add(fileSearchField, + new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, + new Dimension(150, -1), null, 0, false)); final JLabel label5 = new JLabel(); label5.setText("Search for file"); - panel4.add(label5, new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel4.add(label5, new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); searchContainingCheckBox = new JCheckBox(); searchContainingCheckBox.setText("File name contains"); - panel4.add(searchContainingCheckBox, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel4.add(searchContainingCheckBox, + new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); descendingCheckBox = new JCheckBox(); descendingCheckBox.setText("Descending"); - panel4.add(descendingCheckBox, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel4.add(descendingCheckBox, + new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); searchResultCount = new JLabel(); searchResultCount.setText(""); - panel4.add(searchResultCount, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel4.add(searchResultCount, + new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); recentDiffsTab = new JPanel(); recentDiffsTab.setLayout(new GridLayoutManager(2, 1, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane.addTab("⚠\uFE0F Recent diffs", recentDiffsTab); final JSplitPane splitPane4 = new JSplitPane(); - recentDiffsTab.add(splitPane4, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); + recentDiffsTab.add(splitPane4, + new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, + new Dimension(200, 200), null, 0, false)); final JScrollPane scrollPane7 = new JScrollPane(); splitPane4.setLeftComponent(scrollPane7); diffTable = new JTable(); @@ -677,38 +767,63 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { scrollPane8.setViewportView(diffDetails); final JLabel label6 = new JLabel(); Font label6Font = this.$$$getFont$$$(null, Font.BOLD, 16, label6.getFont()); - if (label6Font != null) label6.setFont(label6Font); + if (label6Font != null) + label6.setFont(label6Font); label6.setText("Most recently detected diffs"); - recentDiffsTab.add(label6, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + recentDiffsTab.add(label6, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); settingsTab = new JPanel(); settingsTab.setLayout(new GridLayoutManager(4, 1, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane.addTab("⚙\uFE0F Settings", settingsTab); final JSplitPane splitPane5 = new JSplitPane(); - settingsTab.add(splitPane5, new GridConstraints(0, 0, 4, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); + settingsTab.add(splitPane5, + new GridConstraints(0, 0, 4, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, + new Dimension(200, 200), null, 0, false)); final JPanel panel5 = new JPanel(); panel5.setLayout(new GridLayoutManager(7, 1, new Insets(0, 0, 0, 0), -1, -1)); splitPane5.setLeftComponent(panel5); final JLabel label7 = new JLabel(); Font label7Font = this.$$$getFont$$$(null, Font.BOLD, 14, label7.getFont()); - if (label7Font != null) label7.setFont(label7Font); + if (label7Font != null) + label7.setFont(label7Font); label7.setText("Settings"); - panel5.add(label7, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel5.add(label7, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final Spacer spacer1 = new Spacer(); - panel5.add(spacer1, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); + panel5.add(spacer1, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_CENTER, + GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); themePicker = new JComboBox(); - panel5.add(themePicker, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel5.add(themePicker, + new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); restartButton = new JButton(); restartButton.setText("Restart"); - panel5.add(restartButton, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel5.add(restartButton, + new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label8 = new JLabel(); label8.setText("© 2025 PWSS ORG"); - panel5.add(label8, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel5.add(label8, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JPanel panel6 = new JPanel(); panel6.setLayout(new GridLayoutManager(6, 1, new Insets(0, 0, 0, 0), -1, -1)); - panel5.add(panel6, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); + panel5.add(panel6, + new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, + 0, false)); showSplashScreenCheckBox = new JCheckBox(); showSplashScreenCheckBox.setText("Show splash screen"); - panel6.add(showSplashScreenCheckBox, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel6.add(showSplashScreenCheckBox, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); maxHashExtractionFileSizeSlider = new JSlider(); maxHashExtractionFileSizeSlider.setMajorTickSpacing(5120); maxHashExtractionFileSizeSlider.setMaximum(102400); @@ -718,49 +833,80 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { maxHashExtractionFileSizeSlider.setPaintTicks(true); maxHashExtractionFileSizeSlider.setPaintTrack(true); maxHashExtractionFileSizeSlider.setSnapToTicks(true); - panel6.add(maxHashExtractionFileSizeSlider, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel6.add(maxHashExtractionFileSizeSlider, + new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); final JLabel label9 = new JLabel(); - label9.setText("Max hash exctraction file size (MB)"); - panel6.add(label9, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + label9.setText("Max hash extraction file size (MB)"); + panel6.add(label9, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JSeparator separator1 = new JSeparator(); - panel6.add(separator1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); + panel6.add(separator1, + new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, + false)); maxHashExtractionFileSizeValueLabel = new JLabel(); maxHashExtractionFileSizeValueLabel.setText("Selected:"); - panel6.add(maxHashExtractionFileSizeValueLabel, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel6.add(maxHashExtractionFileSizeValueLabel, + new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); maxHashExtractionFileSizeUnlimitedCheckbox = new JCheckBox(); maxHashExtractionFileSizeUnlimitedCheckbox.setText("Unlimited"); - panel6.add(maxHashExtractionFileSizeUnlimitedCheckbox, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel6.add(maxHashExtractionFileSizeUnlimitedCheckbox, + new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label10 = new JLabel(); label10.setText("Theme"); - panel5.add(label10, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel5.add(label10, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JPanel panel7 = new JPanel(); panel7.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); splitPane5.setRightComponent(panel7); final JScrollPane scrollPane9 = new JScrollPane(); - panel7.add(scrollPane9, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); + panel7.add(scrollPane9, + new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, + 0, false)); quarantineTable = new JTable(); scrollPane9.setViewportView(quarantineTable); final JLabel label11 = new JLabel(); Font label11Font = this.$$$getFont$$$(null, Font.BOLD, 14, label11.getFont()); - if (label11Font != null) label11.setFont(label11Font); + if (label11Font != null) + label11.setFont(label11Font); label11.setText("Quarantined files"); - panel7.add(label11, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel7.add(label11, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); scanProgressContainer = new JPanel(); scanProgressContainer.setLayout(new GridLayoutManager(1, 2, new Insets(10, 10, 10, 10), -1, -1)); - rootPanel.add(scanProgressContainer, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); + rootPanel.add(scanProgressContainer, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_FIXED, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, + 0, true)); scanProgress = new JProgressBar(); scanProgress.setIndeterminate(true); - scanProgressContainer.add(scanProgress, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanProgressContainer.add(scanProgress, + new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); scanProgressLabel = new JLabel(); scanProgressLabel.setText("Scan in progress"); - scanProgressContainer.add(scanProgressLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanProgressContainer.add(scanProgressLabel, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); } /** * @noinspection ALL */ private Font $$$getFont$$$(String fontName, int style, int size, Font currentFont) { - if (currentFont == null) return null; + if (currentFont == null) + return null; String resultName; if (fontName == null) { resultName = currentFont.getName(); @@ -772,9 +918,11 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { resultName = currentFont.getName(); } } - Font font = new Font(resultName, style >= 0 ? style : currentFont.getStyle(), size >= 0 ? size : currentFont.getSize()); + Font font = new Font(resultName, style >= 0 ? style : currentFont.getStyle(), + size >= 0 ? size : currentFont.getSize()); boolean isMac = System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).startsWith("mac"); - Font fontWithFallback = isMac ? new Font(font.getFamily(), font.getStyle(), font.getSize()) : new StyleContext().getFont(font.getFamily(), font.getStyle(), font.getSize()); + Font fontWithFallback = isMac ? new Font(font.getFamily(), font.getStyle(), font.getSize()) + : new StyleContext().getFont(font.getFamily(), font.getStyle(), font.getSize()); return fontWithFallback instanceof FontUIResource ? fontWithFallback : new FontUIResource(fontWithFallback); } From ad4b9940d332539ecb9e4ed0704e08c4c1c810d0 Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Sun, 2 Nov 2025 22:09:35 +0100 Subject: [PATCH 16/44] Updated the Start Script & set log level to INFO --- src/main/resources/logback.xml | 2 +- start.ps1 | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index bf24e35..83566ff 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -11,7 +11,7 @@ - + \ No newline at end of file diff --git a/start.ps1 b/start.ps1 index c6d4600..d389128 100644 --- a/start.ps1 +++ b/start.ps1 @@ -8,14 +8,14 @@ if (-not (Test-Path -Path "..\artifacts")) { if ($null -eq $portInUse) { Write-Host "Nothing is running on port 15400. Starting the process..." - java -jar "..\File-Integrity-Scanner\File-Integrity-Scanner\target\File-Integrity-Scanner-1.5.1.jar" & + java -jar "..\File-Integrity-Scanner\File-Integrity-Scanner\target\File-Integrity-Scanner-1.7.jar" & Write-Host "File-Integrity-Scanner started." - cp .\target\integrity_hash-1.0-jar-with-dependencies.jar ..\artifacts - java -jar ..\artifacts\integrity_hash-1.0-jar-with-dependencies.jar + cp .\target\integrity_hash-1.1-jar-with-dependencies.jar ..\artifacts + java -jar ..\artifacts\integrity_hash-1.1-jar-with-dependencies.jar exit } else { Write-Host "File-Integrity-Scanner is already running on port 15400." - cp .\target\integrity_hash-1.0-jar-with-dependencies.jar ..\artifacts - java -jar ..\artifacts\integrity_hash-1.0-jar-with-dependencies.jar + cp .\target\integrity_hash-1.1-jar-with-dependencies.jar ..\artifacts + java -jar ..\artifacts\integrity_hash-1.1-jar-with-dependencies.jar exit } From 0c52dcecffbd06b4b15102d8f20ee5cf3779d895 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 3 Nov 2025 18:45:44 +0100 Subject: [PATCH 17/44] Implement password complexity validation when creating user and added unit tests for validator :) --- .../org/pwss/controller/LoginController.java | 23 ++----- src/main/java/org/pwss/utils/LoginUtils.java | 49 +++++++++++++ .../java/org/pwss/utils/LoginUtilsTest.java | 68 +++++++++++++++++++ 3 files changed, 124 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/pwss/utils/LoginUtils.java create mode 100644 src/test/java/org/pwss/utils/LoginUtilsTest.java diff --git a/src/main/java/org/pwss/controller/LoginController.java b/src/main/java/org/pwss/controller/LoginController.java index 7e3a4a4..ec5f3bb 100644 --- a/src/main/java/org/pwss/controller/LoginController.java +++ b/src/main/java/org/pwss/controller/LoginController.java @@ -10,6 +10,7 @@ import org.pwss.navigation.NavigationEvents; import org.pwss.navigation.Screen; import org.pwss.service.AuthService; +import org.pwss.utils.LoginUtils; import org.pwss.view.screen.LoginScreen; import org.slf4j.LoggerFactory; @@ -144,31 +145,21 @@ private void proceedAndValidate() { } /** - * Validates the input fields for username and password. + * Validates the user input for username, password, and license key. * - * @return true if both fields are non-empty, false otherwise. + * @return true if input is valid, false otherwise. */ private boolean validateInput() { String username = getScreen().getUsername(); String password = getScreen().getPassword(); String licenseKey = licenseKeySet ? LICENSE_KEY : getScreen().getLicenseKey(); - if (username == null || username.trim().isEmpty()) { - getScreen().showError("Username cannot be empty."); - return false; - } - - if (password == null || password.trim().isEmpty()) { - getScreen().showError("Password cannot be empty."); - return false; - } - - if (licenseKey == null || licenseKey.trim().isEmpty()) { - getScreen().showError("License Key cannot be empty."); - return false; + LoginUtils.LoginValidationResult result = LoginUtils.validateInput(username, password, licenseKey, createUserMode); + if (!result.isValid()) { + getScreen().showError(result.errorMessage()); } - return true; + return result.isValid(); } /** diff --git a/src/main/java/org/pwss/utils/LoginUtils.java b/src/main/java/org/pwss/utils/LoginUtils.java new file mode 100644 index 0000000..ce879eb --- /dev/null +++ b/src/main/java/org/pwss/utils/LoginUtils.java @@ -0,0 +1,49 @@ +package org.pwss.utils; + +/** + * Utility class for validating login inputs. + */ +public final class LoginUtils { + + /** + * Result of login validation. + * + * @param isValid True if the input is valid, false otherwise. + * @param errorMessage The error message if invalid, null if valid. + */ + public record LoginValidationResult(boolean isValid, String errorMessage) {} + + /** + * Validates the user input for username, password, and license key. + * + * @param username The username input. + * @param password The password input. + * @param licenseKey The license key input. + * @param createUserMode True if in user creation mode, false otherwise. + * @return A LoginValidationResult indicating whether the input is valid and any error message. + */ + public static LoginValidationResult validateInput(String username, String password, String licenseKey, boolean createUserMode) { + if (username == null || username.trim().isEmpty()) { + return new LoginValidationResult(false, "Username cannot be empty."); + } + if (password == null || password.trim().isEmpty()) { + return new LoginValidationResult(false, "Password cannot be empty."); + } + if (createUserMode && password.length() < 8) { + return new LoginValidationResult(false, "Password must be at least 8 characters long."); + } + if (createUserMode && !password.matches(".*[A-Z].*")) { + return new LoginValidationResult(false, "Password must contain at least one uppercase letter."); + } + if (createUserMode && !password.matches(".*\\d.*")) { + return new LoginValidationResult(false, "Password must contain at least one digit."); + } + if (createUserMode && !password.matches(".*[!@#$%^&*(),.?\":{}|<>].*")) { + return new LoginValidationResult(false, "Password must contain at least one special character."); + } + if (licenseKey == null || licenseKey.trim().isEmpty()) { + return new LoginValidationResult(false, "License key cannot be empty."); + } + return new LoginValidationResult(true, null); + } +} diff --git a/src/test/java/org/pwss/utils/LoginUtilsTest.java b/src/test/java/org/pwss/utils/LoginUtilsTest.java new file mode 100644 index 0000000..ad19b2a --- /dev/null +++ b/src/test/java/org/pwss/utils/LoginUtilsTest.java @@ -0,0 +1,68 @@ +package org.pwss.utils; + +import org.junit.jupiter.api.Test; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LoginUtilsTest { + + @Test + void validateInput_returnsValidResult_whenAllInputsAreValid() { + var result = LoginUtils.validateInput("validUser", "Valid123!", "LICENSE123", false); + assertTrue(result.isValid()); + assertNull(result.errorMessage()); + } + + @Test + void validateInput_returnsError_whenUsernameIsEmpty() { + var result = LoginUtils.validateInput("", "Valid123!", "LICENSE123", false); + assertFalse(result.isValid()); + assertEquals("Username cannot be empty.", result.errorMessage()); + } + + @Test + void validateInput_returnsError_whenPasswordIsEmpty() { + var result = LoginUtils.validateInput("validUser", "", "LICENSE123", false); + assertFalse(result.isValid()); + assertEquals("Password cannot be empty.", result.errorMessage()); + } + + @Test + void validateInput_returnsError_whenPasswordTooShortInCreateUserMode() { + var result = LoginUtils.validateInput("validUser", "Short1!", "LICENSE123", true); + assertFalse(result.isValid()); + assertEquals("Password must be at least 8 characters long.", result.errorMessage()); + } + + @Test + void validateInput_returnsError_whenPasswordMissingUppercaseInCreateUserMode() { + var result = LoginUtils.validateInput("validUser", "valid123!", "LICENSE123", true); + assertFalse(result.isValid()); + assertEquals("Password must contain at least one uppercase letter.", result.errorMessage()); + } + + @Test + void validateInput_returnsError_whenPasswordMissingDigitInCreateUserMode() { + var result = LoginUtils.validateInput("validUser", "ValidPass!", "LICENSE123", true); + assertFalse(result.isValid()); + assertEquals("Password must contain at least one digit.", result.errorMessage()); + } + + @Test + void validateInput_returnsError_whenPasswordMissingSpecialCharacterInCreateUserMode() { + var result = LoginUtils.validateInput("validUser", "Valid1234", "LICENSE123", true); + assertFalse(result.isValid()); + assertEquals("Password must contain at least one special character.", result.errorMessage()); + } + + @Test + void validateInput_returnsError_whenLicenseKeyIsEmpty() { + var result = LoginUtils.validateInput("validUser", "Valid123!", "", false); + assertFalse(result.isValid()); + assertEquals("License key cannot be empty.", result.errorMessage()); + } +} From 477a4d8402ad3fa6cb04465deb5c7040670e3fe0 Mon Sep 17 00:00:00 2001 From: Stefan <130162586+lilstiffy@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:50:27 +0100 Subject: [PATCH 18/44] Update src/main/java/org/pwss/utils/LoginUtils.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/org/pwss/utils/LoginUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/pwss/utils/LoginUtils.java b/src/main/java/org/pwss/utils/LoginUtils.java index ce879eb..e787c96 100644 --- a/src/main/java/org/pwss/utils/LoginUtils.java +++ b/src/main/java/org/pwss/utils/LoginUtils.java @@ -5,6 +5,10 @@ */ public final class LoginUtils { + // Private constructor to prevent instantiation + private LoginUtils() { + // This constructor is intentionally empty. + } /** * Result of login validation. * From 989ea50eeb1a8b8a005f8986b263833030dbb0a3 Mon Sep 17 00:00:00 2001 From: Stefan <130162586+lilstiffy@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:50:49 +0100 Subject: [PATCH 19/44] Update src/main/java/org/pwss/utils/LoginUtils.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/org/pwss/utils/LoginUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/pwss/utils/LoginUtils.java b/src/main/java/org/pwss/utils/LoginUtils.java index e787c96..7bc0539 100644 --- a/src/main/java/org/pwss/utils/LoginUtils.java +++ b/src/main/java/org/pwss/utils/LoginUtils.java @@ -33,7 +33,7 @@ public static LoginValidationResult validateInput(String username, String passwo if (password == null || password.trim().isEmpty()) { return new LoginValidationResult(false, "Password cannot be empty."); } - if (createUserMode && password.length() < 8) { + if (createUserMode && password.trim().length() < 8) { return new LoginValidationResult(false, "Password must be at least 8 characters long."); } if (createUserMode && !password.matches(".*[A-Z].*")) { From 32755bfbc672a6ac2ebd3017cfbd7b50aa79cbf8 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 3 Nov 2025 19:16:47 +0100 Subject: [PATCH 20/44] Editable includeSubDirs & active status for a monitored directory --- .../service/MonitoredDirectoryService.java | 74 +++++++++++++------ .../java/org/pwss/utils/StringConstants.java | 9 ++- .../MonitoredDirectoryPopupFactory.java | 32 ++++++-- .../MonitoredDirectoryPopupListener.java | 13 +++- .../MonitoredDirectoryPopupListenerImpl.java | 29 +++++++- 5 files changed, 120 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/pwss/service/MonitoredDirectoryService.java b/src/main/java/org/pwss/service/MonitoredDirectoryService.java index 8477cf2..8e3b9bb 100644 --- a/src/main/java/org/pwss/service/MonitoredDirectoryService.java +++ b/src/main/java/org/pwss/service/MonitoredDirectoryService.java @@ -153,47 +153,77 @@ public MonitoredDirectory createNewMonitoredDirectory(String path, boolean inclu } /** - * Updates an existing monitored directory by sending a request to the - * MONITORED_DIRECTORY_UPDATE endpoint. + * Toggles the active status of a monitored directory by sending a request to + * the MONITORED_DIRECTORY_UPDATE endpoint. * - * @param id The ID of the monitored directory to update. - * @param isActive The new active status of the monitored directory. - * @param notes Any notes associated with the monitored directory. - * @param includeSubDirs Whether to include subdirectories in monitoring. + * @param dir The MonitoredDirectory object representing the directory to be + * updated. * @return `true` if the update was successful, otherwise false. * @throws UpdateMonitoredDirectoryException If the update attempt fails due to - * invalid input, unauthorized access, - * or server error. + * invalid input, unauthorized access, + * or server error. * @throws ExecutionException If an error occurs during the - * asynchronous execution of the - * request. + * asynchronous execution of the request. * @throws InterruptedException If the thread executing the request - * is interrupted. + * is interrupted. * @throws JsonProcessingException If an error occurs while - * serializing the request body. + * serializing the request body. */ - public boolean updateMonitoredDirectory(long id, boolean isActive, String notes, boolean includeSubDirs) - throws UpdateMonitoredDirectoryException, JsonProcessingException, ExecutionException, - InterruptedException { + public boolean toggleActive(MonitoredDirectory dir) throws UpdateMonitoredDirectoryException, JsonProcessingException, ExecutionException, InterruptedException { String body = objectMapper - .writeValueAsString(new UpdateDirectoryRequest(id, isActive, notes, includeSubDirs)); + .writeValueAsString(new UpdateDirectoryRequest(dir.id(), !dir.isActive(), dir.notes().notes(), dir.includeSubdirectories())); HttpResponse response = PwssHttpClient.getInstance() .request(Endpoint.MONITORED_DIRECTORY_UPDATE, body); return switch (response.statusCode()) { case 200 -> true; - case 400 -> throw new UpdateMonitoredDirectoryException( - "Update monitored directory failed: invalid input data."); + case 400 -> + throw new UpdateMonitoredDirectoryException("Update monitored directory failed: invalid input data."); + case 401 -> + throw new UpdateMonitoredDirectoryException("Update monitored directory failed: User not authorized to perform this action."); + case 500 -> + throw new UpdateMonitoredDirectoryException("Update monitored directory failed: An error occurred on the server while attempting to update the monitored directory."); + default -> false; + }; + } + + /** + * Toggles the inclusion of subdirectories for a monitored directory by sending + * a request to the MONITORED_DIRECTORY_UPDATE endpoint. + * + * @param dir The MonitoredDirectory object representing the directory to be + * updated. + * @return `true` if the update was successful, otherwise false. + * @throws UpdateMonitoredDirectoryException If the update attempt fails due to + * invalid input, unauthorized access, + * or server error. + * @throws ExecutionException If an error occurs during the + * asynchronous execution of the request. + * @throws InterruptedException If the thread executing the request + * is interrupted. + * @throws JsonProcessingException If an error occurs while + * serializing the request body. + */ + public boolean toggleIncludeSubDirectories(MonitoredDirectory dir) throws UpdateMonitoredDirectoryException, JsonProcessingException, ExecutionException, InterruptedException { + String body = objectMapper + .writeValueAsString(new UpdateDirectoryRequest(dir.id(), dir.isActive(), dir.notes().notes(), !dir.includeSubdirectories())); + HttpResponse response = PwssHttpClient.getInstance() + .request(Endpoint.MONITORED_DIRECTORY_UPDATE, body); + + return switch (response.statusCode()) { + case 200 -> true; + case 400 -> + throw new UpdateMonitoredDirectoryException("Update monitored directory failed: invalid input data."); case 401 -> - throw new UpdateMonitoredDirectoryException( - "Update monitored directory failed: User not authorized to perform this action."); + throw new UpdateMonitoredDirectoryException("Update monitored directory failed: User not authorized to perform this action."); case 500 -> - throw new UpdateMonitoredDirectoryException( - "Update monitored directory failed: An error occurred on the server while attempting to update the monitored directory."); + throw new UpdateMonitoredDirectoryException("Update monitored directory failed: An error occurred on the server while attempting to update the monitored directory."); default -> false; }; } + + /** * Creates a new baseline for a monitored directory by sending a request to the * MONITORED_DIRECTORY_NEW_BASELINE endpoint. diff --git a/src/main/java/org/pwss/utils/StringConstants.java b/src/main/java/org/pwss/utils/StringConstants.java index 29526f1..25c68ac 100644 --- a/src/main/java/org/pwss/utils/StringConstants.java +++ b/src/main/java/org/pwss/utils/StringConstants.java @@ -48,7 +48,14 @@ public final class StringConstants { public static final String MON_DIR_POPUP_RESET_BASELINE_ERROR = "Failed to reset baseline."; public static final String MON_DIR_POPUP_RESET_BASELINE_ERROR_INVALID = "Invalid code. Baseline reset cancelled."; public static final String MON_DIR_POPUP_RESET_BASELINE_ERROR_PREFIX = "Error resetting baseline: "; - public static final String MON_DIR_POPUP_EDIT_DIR = "Edit this directory"; + public static final String MON_DIR_TOGGLE_ACTIVE_ENABLE = "Set directory as active"; + public static final String MON_DIR_TOGGLE_ACTIVE_DISABLE = "Set directory as inactive"; + public static final String MON_DIR_TOGGLE_ACTIVE_SUCCESS = "Directory status updated successfully."; + public static final String MON_DIR_TOGGLE_ACTIVE_ERROR = "Failed to update directory status."; + public static final String MON_DIR_TOGGLE_INCLUDE_SUBDIR_ENABLE = "Include subdirectories"; + public static final String MON_DIR_TOGGLE_INCLUDE_SUBDIR_DISABLE = "Exclude subdirectories"; + public static final String MON_DIR_TOGGLE_INCLUDE_SUBDIR_SUCCESS = "Subdirectory inclusion status updated successfully."; + public static final String MON_DIR_TOGGLE_INCLUDE_SUBDIR_ERROR = "Failed to update subdirectory inclusion status."; // Show note related strings public static final String MON_DIR_POPUP_SHOW_NOTE = "Show note"; diff --git a/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java b/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java index ade03a6..99c0797 100644 --- a/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java +++ b/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java @@ -49,8 +49,11 @@ public JPopupMenu create(JTable table, int viewRow) { // Reset baseline JMenuItem resetBaselineItem = getResetBaselineItem(dir); - // Edit directory - JMenuItem editDirectory = getEditDirectoryItem(dir); + // Toggle active state + JMenuItem toggleActiveMenuItem = getToggleActiveMenuItem(dir); + + // Toggle include subdirectories + JMenuItem toggleIncludeSubDirsItem = getToggleIncludeSubDirsItem(dir); // Show note JMenuItem showNoteItem = getShowNoteItem(dir); @@ -63,7 +66,8 @@ public JPopupMenu create(JTable table, int viewRow) { // Assemble menu menu.add(scanItem); - menu.add(editDirectory); + menu.add(toggleActiveMenuItem); + menu.add(toggleIncludeSubDirsItem); menu.addSeparator(); menu.add(showNoteItem); menu.add(updateNoteItem); @@ -75,14 +79,26 @@ public JPopupMenu create(JTable table, int viewRow) { } /** - * Creates a menu item for editing the details of a monitored directory. + * Creates a menu item for toggling the active status of a monitored directory. + * + * @param dir the monitored directory for which to create the menu item + * @return the JMenuItem for toggling the active status + */ + private JMenuItem getToggleActiveMenuItem(MonitoredDirectory dir) { + JMenuItem editItem = new JMenuItem(dir.isActive() ? StringConstants.MON_DIR_TOGGLE_ACTIVE_DISABLE : StringConstants.MON_DIR_TOGGLE_ACTIVE_ENABLE); + editItem.addActionListener(e -> listener.onToggleActiveStatus(dir)); + return editItem; + } + + /** + * Creates a menu item for toggling the inclusion of subdirectories in a monitored directory. * * @param dir the monitored directory for which to create the menu item - * @return the JMenuItem for editing the directory + * @return the JMenuItem for toggling the inclusion of subdirectories */ - private JMenuItem getEditDirectoryItem(MonitoredDirectory dir) { - JMenuItem editItem = new JMenuItem(StringConstants.MON_DIR_POPUP_EDIT_DIR); - editItem.addActionListener(e -> listener.onEditDirectory(dir)); + private JMenuItem getToggleIncludeSubDirsItem(MonitoredDirectory dir) { + JMenuItem editItem = new JMenuItem(dir.includeSubdirectories() ? StringConstants.MON_DIR_TOGGLE_INCLUDE_SUBDIR_DISABLE : StringConstants.MON_DIR_TOGGLE_INCLUDE_SUBDIR_ENABLE); + editItem.addActionListener(e -> listener.onToggleIncludeSubdirectories(dir)); return editItem; } diff --git a/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListener.java b/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListener.java index 9cc465a..f81ded5 100644 --- a/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListener.java +++ b/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListener.java @@ -23,11 +23,18 @@ public interface MonitoredDirectoryPopupListener { void onResetBaseline(MonitoredDirectory dir, long endpointCode); /** - * Triggered when a monitored directory is edited. + * Triggered when the active status of a monitored directory is toggled. * - * @param dir The monitored directory to be edited. + * @param dir The monitored directory whose active status is being toggled. */ - void onEditDirectory(MonitoredDirectory dir); + void onToggleActiveStatus(MonitoredDirectory dir); + + /** + * Triggered when the inclusion of subdirectories for a monitored directory is toggled. + * + * @param dir The monitored directory whose subdirectory inclusion is being toggled. + */ + void onToggleIncludeSubdirectories(MonitoredDirectory dir); /** * Triggered when the notes for a monitored directory are updated. diff --git a/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListenerImpl.java b/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListenerImpl.java index 85b1398..1080fab 100644 --- a/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListenerImpl.java +++ b/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListenerImpl.java @@ -68,10 +68,33 @@ public void onResetBaseline(MonitoredDirectory dir, long endpointCode) { } @Override - public void onEditDirectory(MonitoredDirectory dir) { - // TODO: Implement edit directory functionality + public void onToggleActiveStatus(MonitoredDirectory dir) { + try { + if (directoryService.toggleActive(dir)) { + showSuccess(StringConstants.MON_DIR_TOGGLE_ACTIVE_SUCCESS); + controller.reloadData(); + } else { + showError(StringConstants.MON_DIR_TOGGLE_ACTIVE_ERROR); + } + } catch (Exception e) { + log.error("Error toggling active status for directory {}: {}", dir.path(), e.getMessage()); + showError(e.getMessage()); + } + } - // We need to be able to update and save notes. Maybe here? :) / Pwgit-Create + @Override + public void onToggleIncludeSubdirectories(MonitoredDirectory dir) { + try { + if (directoryService.toggleIncludeSubDirectories(dir)) { + showSuccess(StringConstants.MON_DIR_TOGGLE_INCLUDE_SUBDIR_SUCCESS); + controller.reloadData(); + } else { + showError(StringConstants.MON_DIR_TOGGLE_INCLUDE_SUBDIR_ERROR); + } + } catch (Exception e) { + log.error("Error toggling include subdirectories for directory {}: {}", dir.path(), e.getMessage()); + showError(e.getMessage()); + } } @Override From 59d53bfdc8fee8e4e267f12998e9e5f9d6e573bd Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 3 Nov 2025 19:50:39 +0100 Subject: [PATCH 21/44] Fix things that came up from discussion with @pwgit-create --- .../org/pwss/model/table/MonitoredDirectoryTableModel.java | 5 +++-- .../pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java b/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java index cc05bd2..4c3edc8 100644 --- a/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java +++ b/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java @@ -9,7 +9,7 @@ public class MonitoredDirectoryTableModel extends AbstractTableModel { private final List directories; private final String[] columnNames = { - "\uD83D\uDCC1 Path", "\uD83D\uDEA6 Note", "\uD83D\uDD59 Last Scanned", "\uD83D\uDEE1️ Baseline Established", "\uD83D\uDDC2️ Include Subdirectories" + "\uD83D\uDCC1 Path", "\uD83D\uDEA6 Note", "\uD83D\uDD59 Last Scanned", "\uD83D\uDEE1️ Baseline Established", "\uD83D\uDDC2️ Include Subdirectories", "Active" }; public MonitoredDirectoryTableModel(List directories) { @@ -35,7 +35,7 @@ public String getColumnName(int column) { public Class getColumnClass(int columnIndex) { return switch (columnIndex) { case 2 -> Date.class; - case 4 -> Boolean.class; + case 4, 5 -> Boolean.class; default -> super.getColumnClass(columnIndex); }; } @@ -49,6 +49,7 @@ public Object getValueAt(int rowIndex, int columnIndex) { case 2 -> dir.lastScanned(); case 3 -> dir.baselineEstablished() ? "Yes" : "No"; case 4 -> dir.includeSubdirectories(); + case 5 -> dir.isActive(); default -> null; }; } diff --git a/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java b/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java index 99c0797..8447121 100644 --- a/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java +++ b/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java @@ -65,7 +65,9 @@ public JPopupMenu create(JTable table, int viewRow) { JMenuItem restoreNoteItem = getRestoreNoteItem(dir); // Assemble menu - menu.add(scanItem); + if (dir.isActive()) { + menu.add(scanItem); + } menu.add(toggleActiveMenuItem); menu.add(toggleIncludeSubDirsItem); menu.addSeparator(); From 5d21423f89fcab12325f010a16fb80d6dcc1461f Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 3 Nov 2025 20:41:13 +0100 Subject: [PATCH 22/44] Fix unwanted error message caused when having no active monitored directories and trying to fetch their scans --- src/main/java/org/pwss/controller/HomeController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index 645209c..9315817 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -250,8 +250,9 @@ void fetchDataAndRefreshView() { // table allMonitoredDirectories = monitoredDirectoryService.getAllDirectories(); - // Only fetch diffs if there are monitored directories present - if (!allMonitoredDirectories.isEmpty()) { + // Only fetch diffs if there are active monitored directories present + final long activeDirCount = allMonitoredDirectories.stream().filter(MonitoredDirectory::isActive).count(); + if (activeDirCount > 0) { // Fetch recent scans for display in the scan table recentScans = scanService.getMostRecentScansAll(); if (recentScans.isEmpty()) { From 57f81ce4b6379c43e760dbd5a60fb73f4c2b15e9 Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 3 Nov 2025 22:59:01 +0100 Subject: [PATCH 23/44] =?UTF-8?q?=E2=9C=A8=20Update=20emojis,=20add=20Erro?= =?UTF-8?q?r=20Utils=20class,=20and=20improve=20error=20dialog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New "Active" emoji (🔌 plug-in symbol) - Introduced `ErrorUtils` class to handle and format error messages consistently - "Note" emoji: traffic light → page & pen 📝 - "Baseline Established" emoji: previous unclear emoji → anchor ⚓ - "Include Subdirs" emoji: snowboarder 🏂 → open folder 📂 - Enhanced error dialog details shown when starting a full scan without any active monitored directories --- .../org/pwss/controller/HomeController.java | 8 +++++--- .../table/MonitoredDirectoryTableModel.java | 2 +- .../java/org/pwss/service/ScanService.java | 9 ++++----- src/main/java/org/pwss/utils/ErrorUtils.java | 18 ++++++++++++++++++ .../java/org/pwss/utils/StringConstants.java | 4 ++-- 5 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/pwss/utils/ErrorUtils.java diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index 9315817..a7fce75 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -61,6 +61,7 @@ import org.pwss.service.ScanSummaryService; import org.pwss.utils.AppTheme; import org.pwss.utils.ConversionUtils; +import org.pwss.utils.ErrorUtils; import org.pwss.utils.LiveFeedUtils; import org.pwss.utils.MonitoredDirectoryUtils; import org.pwss.utils.OSUtils; @@ -71,7 +72,6 @@ import org.pwss.view.screen.HomeScreen; import org.slf4j.LoggerFactory; - import static org.pwss.app_settings.AppConfig.APP_THEME; import static org.pwss.app_settings.AppConfig.MAX_HASH_EXTRACTION_FILE_SIZE; import static org.pwss.app_settings.AppConfig.USE_SPLASH_SCREEN; @@ -603,7 +603,8 @@ public Component getListCellRendererComponent(JList list, Object value, int i screen.getMaxHashExtractionFileSizeUnlimitedCheckbox().setSelected(false); screen.getMaxHashExtractionFileSizeSlider().setEnabled(true); - final int maxSliderValueMegabytes = Math.toIntExact(ConversionUtils.bytesToMegabytes(maxFileSizeForHashExtraction)); + final int maxSliderValueMegabytes = Math + .toIntExact(ConversionUtils.bytesToMegabytes(maxFileSizeForHashExtraction)); screen.getMaxHashExtractionFileSizeSlider().setValue(maxSliderValueMegabytes); screen.getMaxHashExtractionFileSizeValueLabel().setText(maxSliderValueMegabytes + " MB"); } else { @@ -686,7 +687,8 @@ public void performStartScan(boolean singleDirectory) { | JsonProcessingException e) { log.debug(StringConstants.SCAN_START_ERROR, e); log.error(StringConstants.SCAN_START_ERROR + " {}", e.getMessage()); - SwingUtilities.invokeLater(() -> screen.showError(StringConstants.SCAN_START_ERROR)); + SwingUtilities.invokeLater(() -> screen + .showError(ErrorUtils.formatErrorMessage(StringConstants.SCAN_START_ERROR, e.getMessage()))); } } diff --git a/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java b/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java index 4c3edc8..8f2d2c2 100644 --- a/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java +++ b/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java @@ -9,7 +9,7 @@ public class MonitoredDirectoryTableModel extends AbstractTableModel { private final List directories; private final String[] columnNames = { - "\uD83D\uDCC1 Path", "\uD83D\uDEA6 Note", "\uD83D\uDD59 Last Scanned", "\uD83D\uDEE1️ Baseline Established", "\uD83D\uDDC2️ Include Subdirectories", "Active" + "\uD83D\uDCC1 Path", "\uD83D\uDCDD Note", "\uD83D\uDD59 Last Scanned", "\u2693 Baseline Established", "\uD83D\uDCC2 Include Subdirectories", "\uD83D\uDD0C Active" }; public MonitoredDirectoryTableModel(List directories) { diff --git a/src/main/java/org/pwss/service/ScanService.java b/src/main/java/org/pwss/service/ScanService.java index 2411a0d..7220406 100644 --- a/src/main/java/org/pwss/service/ScanService.java +++ b/src/main/java/org/pwss/service/ScanService.java @@ -6,7 +6,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; -import org.pwss.app_settings.AppConfig; import org.pwss.exception.scan.GetAllMostRecentScansException; import org.pwss.exception.scan.GetMostRecentScansException; import org.pwss.exception.scan.GetScanDiffsException; @@ -54,12 +53,12 @@ public boolean startScan(long maxHashExtractionFileSize) throws StartFullScanExc return switch (response.statusCode()) { case 200 -> true; case 401 -> - throw new StartFullScanException("Start scan all failed: User not authorized to perform this action."); + throw new StartFullScanException("User not authorized to perform this action."); case 412 -> - throw new StartFullScanException("Start scan all failed: No active monitored directories found."); - case 425 -> throw new StartFullScanException("Start scan all failed: Scan is already running."); + throw new StartFullScanException("There are no directories being actively monitored."); + case 425 -> throw new StartFullScanException("Scan is already running."); case 500 -> - throw new StartFullScanException("Start scan all failed: An error occurred on the server while attempting to start the scan."); + throw new StartFullScanException("An error occurred on the server while attempting to start the scan."); default -> false; }; } diff --git a/src/main/java/org/pwss/utils/ErrorUtils.java b/src/main/java/org/pwss/utils/ErrorUtils.java new file mode 100644 index 0000000..f9ae267 --- /dev/null +++ b/src/main/java/org/pwss/utils/ErrorUtils.java @@ -0,0 +1,18 @@ +package org.pwss.utils; + +/** + * Utility class for handling and formatting error messages. + */ +public final class ErrorUtils { + + /** + * Formats an error message by combining a constant text with a dynamic error message. + * + * @param errorConstantText The constant part of the error message, such as "Error: " + * @param errorMessageText The dynamic part of the error message that provides specific details + * @return A formatted error message string combining both parts + */ + public static String formatErrorMessage(String errorConstantText, String errorMessageText) { + return errorConstantText + errorMessageText; + } +} \ No newline at end of file diff --git a/src/main/java/org/pwss/utils/StringConstants.java b/src/main/java/org/pwss/utils/StringConstants.java index 25c68ac..f6f087f 100644 --- a/src/main/java/org/pwss/utils/StringConstants.java +++ b/src/main/java/org/pwss/utils/StringConstants.java @@ -27,8 +27,8 @@ public final class StringConstants { public static final String SCAN_STOPPED_SUCCESS = "Scan stopped successfully!"; public static final String SCAN_STARTED_FAILURE = "Failed to start scan."; public static final String SCAN_STOPPED_FAILURE = "Failed to stop scan."; - public static final String SCAN_START_ERROR = "Error starting scan: "; - public static final String SCAN_STOP_ERROR = "Error stopping scan: "; + public static final String SCAN_START_ERROR = "An error occurred during scan initiation: "; + public static final String SCAN_STOP_ERROR = "An error has occurred while attempting to stop the scan.: "; public static final String SCAN_BASELINE_COMPLETED = "Baseline established successfully!\nDo you wish to see the details?"; public static final String SCAN_COMPLETED_DIFFS = "Scan completed with differences found.\nDo you wish to see the details?\nYou can always view results later in the recent scans table."; public static final String SCAN_COMPLETED_NO_DIFFS = "Scan completed with no differences found.\nDo you wish to see the details?\nYou can always view results later in the recent scans table."; From 408947ca51863a1676ac60da60bef590ac74084e Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 3 Nov 2025 23:57:05 +0100 Subject: [PATCH 24/44] Live feed output has been enhanced --- src/main/java/org/pwss/utils/ErrorUtils.java | 9 +++-- .../java/org/pwss/utils/LiveFeedUtils.java | 35 +++++++++++++++++-- src/main/java/org/pwss/utils/OSUtils.java | 1 - 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/pwss/utils/ErrorUtils.java b/src/main/java/org/pwss/utils/ErrorUtils.java index f9ae267..beefac1 100644 --- a/src/main/java/org/pwss/utils/ErrorUtils.java +++ b/src/main/java/org/pwss/utils/ErrorUtils.java @@ -6,10 +6,13 @@ public final class ErrorUtils { /** - * Formats an error message by combining a constant text with a dynamic error message. + * Formats an error message by combining a constant text with a dynamic error + * message. * - * @param errorConstantText The constant part of the error message, such as "Error: " - * @param errorMessageText The dynamic part of the error message that provides specific details + * @param errorConstantText The constant part of the error message, such as + * "Error: " + * @param errorMessageText The dynamic part of the error message that provides + * specific details * @return A formatted error message string combining both parts */ public static String formatErrorMessage(String errorConstantText, String errorMessageText) { diff --git a/src/main/java/org/pwss/utils/LiveFeedUtils.java b/src/main/java/org/pwss/utils/LiveFeedUtils.java index c687f7b..859a62d 100644 --- a/src/main/java/org/pwss/utils/LiveFeedUtils.java +++ b/src/main/java/org/pwss/utils/LiveFeedUtils.java @@ -1,7 +1,37 @@ package org.pwss.utils; +/** + * Utility class for processing live feed entries. + */ public final class LiveFeedUtils { + /** + * A white check mark emoji used in live feed entries. + */ + private final static String WHITE_CHECK_MARK = "✅"; + /** + * The white check mark emoji with a newline character appended to it. + */ + private final static String WHITE_CHECK_MARK_REPLACE = "✅\n"; + + /** + * A warning emoji used in live feed entries. + */ + private final static String WARNING = "⚠️"; + /** + * The warning emoji with a newline character appended to it. + */ + private final static String WARNING_REPLACE = "⚠️\n"; + + /** + * A message indicating that a file is too big, used in live feed entries. + */ + private final static String FILE_TO_BIG_MESSAGE = "is bigger than the user defined max limit"; + /** + * The file too big message with a newline character appended to it. + */ + private final static String FILE_TO_BIG_MESSAGE_REPLACE = FILE_TO_BIG_MESSAGE + "\n"; + /** * Formats a raw live feed entry for improved readability. * Currently, inserts line breaks after certain emojis. @@ -15,8 +45,9 @@ public static String formatLiveFeedEntry(String rawEntry) { } return rawEntry - .replace("✅", "✅\n") - .replace("⚠️", "⚠️\n") + .replace(WHITE_CHECK_MARK, WHITE_CHECK_MARK_REPLACE) + .replace(WARNING, WARNING_REPLACE) + .replace(FILE_TO_BIG_MESSAGE, FILE_TO_BIG_MESSAGE_REPLACE) .trim(); } diff --git a/src/main/java/org/pwss/utils/OSUtils.java b/src/main/java/org/pwss/utils/OSUtils.java index b86fa59..e0e07d5 100644 --- a/src/main/java/org/pwss/utils/OSUtils.java +++ b/src/main/java/org/pwss/utils/OSUtils.java @@ -105,7 +105,6 @@ public static final boolean isMac() { * @return A warning message string specific to the current OS. */ public static String getQuarantineWarningMessage() { - // TODO: Replace with custom, detailed warning messages for each OS. return switch (determineOSType()) { case WINDOWS -> "Warning: Quarantining or removing Windows system files (drivers, DLLs, registry-related files) can render the system unstable or unbootable. Back up data, ensure you have recovery media and administrator access before proceeding."; From 4e219e209193290416b24a9bcb097d7e9a158770 Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 4 Nov 2025 19:42:50 +0100 Subject: [PATCH 25/44] Changes to LoginUtils and new unit tests, WIP welcome message --- .../org/pwss/controller/LoginController.java | 51 +++--- src/main/java/org/pwss/navigation/Screen.java | 2 +- src/main/java/org/pwss/utils/LoginUtils.java | 122 +++++++++---- .../org/pwss/view/screen/LoginScreen.form | 161 ++++++++++-------- .../org/pwss/view/screen/LoginScreen.java | 147 +++++++++++++--- .../java/org/pwss/utils/LoginUtilsTest.java | 108 ++++++++---- 6 files changed, 407 insertions(+), 184 deletions(-) diff --git a/src/main/java/org/pwss/controller/LoginController.java b/src/main/java/org/pwss/controller/LoginController.java index ec5f3bb..a1432e4 100644 --- a/src/main/java/org/pwss/controller/LoginController.java +++ b/src/main/java/org/pwss/controller/LoginController.java @@ -59,8 +59,8 @@ public LoginController(LoginScreen view) { @Override public void onShow() { - getScreen().getUsernameField().setText(""); - getScreen().getPasswordField().setText(""); + screen.getUsernameField().setText(""); + screen.getPasswordField().setText(""); log.debug("Current LICENSE_KEY: {}", licenseKeySet ? "SET" : "NOT SET"); log.debug("Create User Mode: {}", createUserMode); refreshView(); @@ -68,32 +68,36 @@ public void onShow() { @Override protected void initListeners() { - getScreen().getPasswordField().addActionListener(e -> proceedAndValidate()); - getScreen().getProceedButton().addActionListener(e -> proceedAndValidate()); - getScreen().getCancelButton().addActionListener(e -> System.exit(0)); + screen.getPasswordField().addActionListener(e -> proceedAndValidate()); + screen.getProceedButton().addActionListener(e -> proceedAndValidate()); + screen.getCancelButton().addActionListener(e -> System.exit(0)); } @Override protected void refreshView() { // Only show license key fields if LICENSE_KEY is not set if (licenseKeySet) { - getScreen().getLicenseLabel().setVisible(false); - getScreen().getLicenseKeyField().setVisible(false); + screen.getLicenseLabel().setVisible(false); + screen.getLicenseKeyField().setVisible(false); } // Update the view based on whether we are in create user mode if (createUserMode) { // Notify user that no users exist and they need to create one - getScreen().showInfo("No user found.\nCreate one by entering a username and password."); + screen.showInfo("No user found.\nCreate one by entering a username and password."); // Update message label to indicate user creation - getScreen().setMessage("Create a user for the first login."); + screen.setMessage("Create a user for the first login."); // Change button text to "Register" - getScreen().getProceedButton().setText("Register"); + screen.getProceedButton().setText("Register"); } else { // Update message label to indicate normal login - getScreen().setMessage("Login with your username and password."); + screen.setMessage("Login with your username and password."); // Change button text to "Login" - getScreen().getProceedButton().setText("Login"); + screen.getProceedButton().setText("Login"); } + + // Show or hide confirm password fields based on create user mode + screen.getConfirmPasswordLabel().setVisible(createUserMode); + screen.getConfirmPasswordField().setVisible(createUserMode); } /** @@ -150,13 +154,14 @@ private void proceedAndValidate() { * @return true if input is valid, false otherwise. */ private boolean validateInput() { - String username = getScreen().getUsername(); - String password = getScreen().getPassword(); - String licenseKey = licenseKeySet ? LICENSE_KEY : getScreen().getLicenseKey(); + String username = screen.getUsername(); + String password = screen.getPassword(); + String confirmPassword = screen.getConfirmPassword(); + String licenseKey = licenseKeySet ? LICENSE_KEY : screen.getLicenseKey(); - LoginUtils.LoginValidationResult result = LoginUtils.validateInput(username, password, licenseKey, createUserMode); + LoginUtils.LoginValidationResult result = LoginUtils.validateInput(username, password, confirmPassword, licenseKey, createUserMode); if (!result.isValid()) { - getScreen().showError(result.errorMessage()); + screen.showError(LoginUtils.formatErrors(result.errors())); } return result.isValid(); @@ -177,9 +182,9 @@ private void createUserAndLogin() { * @return true if user creation is successful, false otherwise. */ private boolean createUser() { - String username = getScreen().getUsername(); - String password = getScreen().getPassword(); - String licenseKey = licenseKeySet ? LICENSE_KEY : getScreen().getLicenseKey(); + String username = screen.getUsername(); + String password = screen.getPassword(); + String licenseKey = licenseKeySet ? LICENSE_KEY : screen.getLicenseKey(); try { final boolean createSuccess = authService.createUser(username, password, licenseKey); @@ -215,9 +220,9 @@ private boolean createUser() { * Displays success or error messages based on the outcome. */ private void performLogin() { - String username = getScreen().getUsername(); - String password = getScreen().getPassword(); - String licenseKey = licenseKeySet ? LICENSE_KEY : getScreen().getLicenseKey(); + String username = screen.getUsername(); + String password = screen.getPassword(); + String licenseKey = licenseKeySet ? LICENSE_KEY : screen.getLicenseKey(); try { final boolean loginSuccess = authService.login(username, password, licenseKey); diff --git a/src/main/java/org/pwss/navigation/Screen.java b/src/main/java/org/pwss/navigation/Screen.java index 36092fc..b00ac91 100644 --- a/src/main/java/org/pwss/navigation/Screen.java +++ b/src/main/java/org/pwss/navigation/Screen.java @@ -7,7 +7,7 @@ public enum Screen { /** * The login screen where users can authenticate. */ - LOGIN(400, 200), + LOGIN(450, 350), /** * The home screen displayed after successful login. diff --git a/src/main/java/org/pwss/utils/LoginUtils.java b/src/main/java/org/pwss/utils/LoginUtils.java index 7bc0539..a0a002f 100644 --- a/src/main/java/org/pwss/utils/LoginUtils.java +++ b/src/main/java/org/pwss/utils/LoginUtils.java @@ -1,5 +1,9 @@ package org.pwss.utils; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + /** * Utility class for validating login inputs. */ @@ -9,45 +13,101 @@ public final class LoginUtils { private LoginUtils() { // This constructor is intentionally empty. } + /** - * Result of login validation. + * Validates the login input fields. * - * @param isValid True if the input is valid, false otherwise. - * @param errorMessage The error message if invalid, null if valid. + * @param username The username input. + * @param password The password input. + * @param confirmPassword The confirm password input (used in create user mode). + * @param licenseKey The license key input. + * @param createUserMode Flag indicating if the application is in create user mode. + * @return A LoginValidationResult containing validation status and error messages. */ - public record LoginValidationResult(boolean isValid, String errorMessage) {} + public static LoginValidationResult validateInput( + String username, + String password, + String confirmPassword, + String licenseKey, + boolean createUserMode + ) { + List errors = new ArrayList<>(); + + username = username == null ? "" : username.trim(); + password = password == null ? "" : password.trim(); + confirmPassword = confirmPassword == null ? "" : confirmPassword.trim(); + licenseKey = licenseKey == null ? "" : licenseKey.trim(); + + if (username.isEmpty()) errors.add("Username cannot be empty."); + if (password.isEmpty()) errors.add("Password cannot be empty."); + if (licenseKey.isEmpty()) errors.add("License key cannot be empty."); + + if (createUserMode && !password.isEmpty()) { + if (password.length() < 8) + errors.add("Password must be at least 8 characters long."); + + if (confirmPassword.isEmpty()) + errors.add("Confirm Password cannot be empty."); + else if (!xorEquals(password, confirmPassword)) + errors.add("Password and Confirm Password do not match."); + + if (!password.matches(".*[A-Z].*")) + errors.add("Password must contain at least one uppercase letter."); + if (!password.matches(".*\\d.*")) + errors.add("Password must contain at least one digit."); + if (!password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?].*")) + errors.add("Password must contain at least one special character."); + } + + return new LoginValidationResult(errors.isEmpty(), errors); + } /** - * Validates the user input for username, password, and license key. + * Formats a list of error messages into a single string. * - * @param username The username input. - * @param password The password input. - * @param licenseKey The license key input. - * @param createUserMode True if in user creation mode, false otherwise. - * @return A LoginValidationResult indicating whether the input is valid and any error message. + * @param errors List of error messages. + * @return Formatted error string. */ - public static LoginValidationResult validateInput(String username, String password, String licenseKey, boolean createUserMode) { - if (username == null || username.trim().isEmpty()) { - return new LoginValidationResult(false, "Username cannot be empty."); - } - if (password == null || password.trim().isEmpty()) { - return new LoginValidationResult(false, "Password cannot be empty."); - } - if (createUserMode && password.trim().length() < 8) { - return new LoginValidationResult(false, "Password must be at least 8 characters long."); - } - if (createUserMode && !password.matches(".*[A-Z].*")) { - return new LoginValidationResult(false, "Password must contain at least one uppercase letter."); - } - if (createUserMode && !password.matches(".*\\d.*")) { - return new LoginValidationResult(false, "Password must contain at least one digit."); - } - if (createUserMode && !password.matches(".*[!@#$%^&*(),.?\":{}|<>].*")) { - return new LoginValidationResult(false, "Password must contain at least one special character."); + public static String formatErrors(List errors) { + return String.join("\n", errors); + } + + + /** + * Compares two strings for equality using XOR operation. + * + * @param s1 First string. + * @param s2 Second string. + * @return True if both strings are equal, false otherwise. + * @throws IllegalArgumentException if either string is null. + */ + private static boolean xorEquals(String s1, String s2) { + if (s1 == null || s2 == null) { + throw new IllegalArgumentException("Strings to compare cannot be null"); } - if (licenseKey == null || licenseKey.trim().isEmpty()) { - return new LoginValidationResult(false, "License key cannot be empty."); + + byte[] a = s1.getBytes(StandardCharsets.UTF_8); + byte[] b = s2.getBytes(StandardCharsets.UTF_8); + + int maxLen = Math.max(a.length, b.length); + int result = a.length ^ b.length; + + for (int i = 0; i < maxLen; i++) { + byte ba = (i < a.length) ? a[i] : 0; + byte bb = (i < b.length) ? b[i] : 0; + result |= (ba ^ bb); } - return new LoginValidationResult(true, null); + + return result == 0; + } + + /** + * Result of login validation. + * + * @param isValid True if the input is valid, false otherwise. + * @param errors List of error messages if the input is invalid. + */ + public record LoginValidationResult(boolean isValid, List errors) { } } + diff --git a/src/main/java/org/pwss/view/screen/LoginScreen.form b/src/main/java/org/pwss/view/screen/LoginScreen.form index f3ace95..8877f19 100644 --- a/src/main/java/org/pwss/view/screen/LoginScreen.form +++ b/src/main/java/org/pwss/view/screen/LoginScreen.form @@ -1,97 +1,122 @@
- + - + - - - - - - - - - - - - - - - - - - - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + - + - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/org/pwss/view/screen/LoginScreen.java b/src/main/java/org/pwss/view/screen/LoginScreen.java index 3cbeef4..4342a04 100644 --- a/src/main/java/org/pwss/view/screen/LoginScreen.java +++ b/src/main/java/org/pwss/view/screen/LoginScreen.java @@ -2,6 +2,7 @@ import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.core.GridLayoutManager; +import com.intellij.uiDesigner.core.Spacer; import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; @@ -30,6 +31,8 @@ public class LoginScreen extends BaseScreen { private JPanel rootPanel; private JTextField licenseKeyField; private JLabel licenseLabel; + private JPasswordField confirmPasswordField; + private JLabel confirmPasswordLabel; @Override protected String getScreenName() { @@ -41,45 +44,123 @@ public JPanel getRootPanel() { return rootPanel; } + /** + * Sets the message to be displayed on the login screen. + * + * @param message the message to display + */ + public void setMessage(String message) { + this.messageLabel.setText(message); + } + + /** + * Gets the username entered by the user. + * + * @return the username as a String + */ public String getUsername() { return usernameField.getText().trim(); } + /** + * Gets the password entered by the user. + * + * @return the password as a String + */ public String getPassword() { return new String(passwordField.getPassword()); } + /** + * Gets the confirm password entered by the user. + * + * @return the confirm password as a String + */ + public String getConfirmPassword() { + return new String(confirmPasswordField.getPassword()); + } + + /** + * Gets the license key entered by the user. + * + * @return the license key as a String + */ public String getLicenseKey() { return licenseKeyField.getText().trim(); } + /** + * Gets the proceed button. + * + * @return the proceed JButton + */ public JButton getProceedButton() { return proceedButton; } + /** + * Gets the cancel button. + * + * @return the cancel JButton + */ public JButton getCancelButton() { return cancelButton; } + /** + * Gets the password field. + * + * @return the password JPasswordField + */ + public JPasswordField getPasswordField() { + return passwordField; + } + + /** + * Gets the username label. + * + * @return the username JLabel + */ public JTextField getUsernameField() { return usernameField; } - public JPasswordField getPasswordField() { - return passwordField; + /** + * Gets the confirm password label. + * + * @return the confirm password JLabel + */ + public JLabel getConfirmPasswordLabel() { + return confirmPasswordLabel; } - public void setMessage(String message) { - this.messageLabel.setText(message); + /** + * Gets the confirm password field. + * + * @return the confirm password JPasswordField + */ + public JPasswordField getConfirmPasswordField() { + return confirmPasswordField; } + /** + * Gets the license label. + * + * @return the license JLabel + */ + public JLabel getLicenseLabel() { + return licenseLabel; + } + + /** + * Gets the license key text field. + * + * @return the license key JTextField + */ public JTextField getLicenseKeyField() { return licenseKeyField; } - public JLabel getLicenseLabel() { - return licenseLabel; - } { // GUI initializer generated by IntelliJ IDEA GUI Designer @@ -97,43 +178,53 @@ public JLabel getLicenseLabel() { */ private void $$$setupUI$$$() { rootPanel = new JPanel(); - rootPanel.setLayout(new GridLayoutManager(5, 3, new Insets(10, 10, 10, 10), -1, -1, false, true)); - usernameLabel = new JLabel(); - Font usernameLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, usernameLabel.getFont()); - if (usernameLabelFont != null) usernameLabel.setFont(usernameLabelFont); - usernameLabel.setText("Username"); - rootPanel.add(usernameLabel, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - passwordLabel = new JLabel(); - Font passwordLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, passwordLabel.getFont()); - if (passwordLabelFont != null) passwordLabel.setFont(passwordLabelFont); - passwordLabel.setText("Password"); - rootPanel.add(passwordLabel, new GridConstraints(2, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + rootPanel.setLayout(new GridLayoutManager(6, 3, new Insets(10, 10, 10, 10), -1, -1)); final JPanel panel1 = new JPanel(); - panel1.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1)); - rootPanel.add(panel1, new GridConstraints(4, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); + panel1.setLayout(new GridLayoutManager(10, 3, new Insets(0, 0, 0, 0), -1, -1)); + rootPanel.add(panel1, new GridConstraints(0, 0, 6, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); + final JPanel panel2 = new JPanel(); + panel2.setLayout(new GridLayoutManager(1, 2, new Insets(10, 0, 0, 0), -1, -1, true, false)); + panel1.add(panel2, new GridConstraints(9, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); proceedButton = new JButton(); proceedButton.setText("Button"); - panel1.add(proceedButton, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel2.add(proceedButton, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); cancelButton = new JButton(); cancelButton.setText("Cancel"); - panel1.add(cancelButton, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel2.add(cancelButton, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); messageLabel = new JLabel(); - Font messageLabelFont = this.$$$getFont$$$(null, Font.PLAIN, -1, messageLabel.getFont()); + Font messageLabelFont = this.$$$getFont$$$(null, Font.BOLD, 12, messageLabel.getFont()); if (messageLabelFont != null) messageLabel.setFont(messageLabelFont); messageLabel.setText(""); - rootPanel.add(messageLabel, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(messageLabel, new GridConstraints(0, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); passwordField = new JPasswordField(); - rootPanel.add(passwordField, new GridConstraints(2, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(passwordField, new GridConstraints(4, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); usernameField = new JTextField(); usernameField.setText(""); - rootPanel.add(usernameField, new GridConstraints(1, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(usernameField, new GridConstraints(2, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); licenseKeyField = new JTextField(); - rootPanel.add(licenseKeyField, new GridConstraints(3, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); + panel1.add(licenseKeyField, new GridConstraints(8, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); + confirmPasswordField = new JPasswordField(); + panel1.add(confirmPasswordField, new GridConstraints(6, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + passwordLabel = new JLabel(); + Font passwordLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, passwordLabel.getFont()); + if (passwordLabelFont != null) passwordLabel.setFont(passwordLabelFont); + passwordLabel.setText("Password"); + panel1.add(passwordLabel, new GridConstraints(3, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + confirmPasswordLabel = new JLabel(); + Font confirmPasswordLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, confirmPasswordLabel.getFont()); + if (confirmPasswordLabelFont != null) confirmPasswordLabel.setFont(confirmPasswordLabelFont); + confirmPasswordLabel.setText("Confirm password"); + panel1.add(confirmPasswordLabel, new GridConstraints(5, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); licenseLabel = new JLabel(); Font licenseLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, licenseLabel.getFont()); if (licenseLabelFont != null) licenseLabel.setFont(licenseLabelFont); licenseLabel.setText("License key"); - rootPanel.add(licenseLabel, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(licenseLabel, new GridConstraints(7, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + usernameLabel = new JLabel(); + Font usernameLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, usernameLabel.getFont()); + if (usernameLabelFont != null) usernameLabel.setFont(usernameLabelFont); + usernameLabel.setText("Username"); + panel1.add(usernameLabel, new GridConstraints(1, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); } /** diff --git a/src/test/java/org/pwss/utils/LoginUtilsTest.java b/src/test/java/org/pwss/utils/LoginUtilsTest.java index ad19b2a..b7ca8d6 100644 --- a/src/test/java/org/pwss/utils/LoginUtilsTest.java +++ b/src/test/java/org/pwss/utils/LoginUtilsTest.java @@ -1,68 +1,110 @@ package org.pwss.utils; +import java.util.List; import org.junit.jupiter.api.Test; +import org.pwss.utils.LoginUtils.LoginValidationResult; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; public class LoginUtilsTest { - @Test - void validateInput_returnsValidResult_whenAllInputsAreValid() { - var result = LoginUtils.validateInput("validUser", "Valid123!", "LICENSE123", false); - assertTrue(result.isValid()); - assertNull(result.errorMessage()); + // --- Helper for readable assertions --- + private void assertHasError(LoginValidationResult result, String expectedError) { + assertFalse(result.isValid(), "Expected validation to fail"); + assertTrue(result.errors().contains(expectedError), + "Expected error message: " + expectedError); } @Test - void validateInput_returnsError_whenUsernameIsEmpty() { - var result = LoginUtils.validateInput("", "Valid123!", "LICENSE123", false); + void testAllFieldsEmpty() { + LoginValidationResult result = LoginUtils.validateInput("", "", "", "", false); assertFalse(result.isValid()); - assertEquals("Username cannot be empty.", result.errorMessage()); + assertEquals(3, result.errors().size()); + assertHasError(result, "Username cannot be empty."); + assertHasError(result, "Password cannot be empty."); + assertHasError(result, "License key cannot be empty."); } @Test - void validateInput_returnsError_whenPasswordIsEmpty() { - var result = LoginUtils.validateInput("validUser", "", "LICENSE123", false); - assertFalse(result.isValid()); - assertEquals("Password cannot be empty.", result.errorMessage()); + void testNullInputsHandledGracefully() { + assertDoesNotThrow(() -> { + LoginValidationResult result = LoginUtils.validateInput(null, null, null, null, false); + assertFalse(result.isValid()); + assertTrue(result.errors().contains("Username cannot be empty.")); + }); } + // --- License key validation --- + @Test - void validateInput_returnsError_whenPasswordTooShortInCreateUserMode() { - var result = LoginUtils.validateInput("validUser", "Short1!", "LICENSE123", true); - assertFalse(result.isValid()); - assertEquals("Password must be at least 8 characters long.", result.errorMessage()); + void testMissingLicenseKey() { + LoginValidationResult result = LoginUtils.validateInput("user", "password", "", "", false); + assertHasError(result, "License key cannot be empty."); } + // --- Password validation in createUserMode --- + @Test - void validateInput_returnsError_whenPasswordMissingUppercaseInCreateUserMode() { - var result = LoginUtils.validateInput("validUser", "valid123!", "LICENSE123", true); - assertFalse(result.isValid()); - assertEquals("Password must contain at least one uppercase letter.", result.errorMessage()); + void testPasswordTooShort() { + LoginValidationResult result = LoginUtils.validateInput("user", "Ab1!", "Ab1!", "key123", true); + assertHasError(result, "Password must be at least 8 characters long."); } @Test - void validateInput_returnsError_whenPasswordMissingDigitInCreateUserMode() { - var result = LoginUtils.validateInput("validUser", "ValidPass!", "LICENSE123", true); - assertFalse(result.isValid()); - assertEquals("Password must contain at least one digit.", result.errorMessage()); + void testConfirmPasswordMissing() { + LoginValidationResult result = LoginUtils.validateInput("user", "Abcd123!", "", "key123", true); + assertHasError(result, "Confirm Password cannot be empty."); } @Test - void validateInput_returnsError_whenPasswordMissingSpecialCharacterInCreateUserMode() { - var result = LoginUtils.validateInput("validUser", "Valid1234", "LICENSE123", true); - assertFalse(result.isValid()); - assertEquals("Password must contain at least one special character.", result.errorMessage()); + void testPasswordsDoNotMatch() { + LoginValidationResult result = LoginUtils.validateInput("user", "Abcd123!", "Abcd1234!", "key123", true); + assertHasError(result, "Password and Confirm Password do not match."); } @Test - void validateInput_returnsError_whenLicenseKeyIsEmpty() { - var result = LoginUtils.validateInput("validUser", "Valid123!", "", false); - assertFalse(result.isValid()); - assertEquals("License key cannot be empty.", result.errorMessage()); + void testPasswordMissingUppercase() { + LoginValidationResult result = LoginUtils.validateInput("user", "abcd123!", "abcd123!", "key123", true); + assertHasError(result, "Password must contain at least one uppercase letter."); + } + + @Test + void testPasswordMissingDigit() { + LoginValidationResult result = LoginUtils.validateInput("user", "Abcdefg!", "Abcdefg!", "key123", true); + assertHasError(result, "Password must contain at least one digit."); + } + + @Test + void testPasswordMissingSpecialCharacter() { + LoginValidationResult result = LoginUtils.validateInput("user", "Abcdefg1", "Abcdefg1", "key123", true); + assertHasError(result, "Password must contain at least one special character."); + } + + // --- Valid case --- + + @Test + void testValidInputInCreateUserMode() { + LoginValidationResult result = LoginUtils.validateInput("user", "Abcd123!", "Abcd123!", "key123", true); + assertTrue(result.isValid(), "Expected validation to pass"); + assertTrue(result.errors().isEmpty(), "Expected no errors"); + } + + @Test + void testValidInputInLoginMode() { + LoginValidationResult result = LoginUtils.validateInput("user", "password", "", "key123", false); + assertTrue(result.isValid(), "Expected validation to pass in login mode"); + } + + // --- formatErrors() --- + + @Test + void testFormatErrors() { + List errors = List.of("Error 1", "Error 2", "Error 3"); + String formatted = LoginUtils.formatErrors(errors); + assertEquals("Error 1\nError 2\nError 3", formatted); } } From a88893918f69763130d4b82c8770422fa1d6ce05 Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 4 Nov 2025 19:53:15 +0100 Subject: [PATCH 26/44] Welcome dialog when creating user reminding of importance of remembering credentials --- .../org/pwss/controller/LoginController.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/pwss/controller/LoginController.java b/src/main/java/org/pwss/controller/LoginController.java index a1432e4..e4c69bc 100644 --- a/src/main/java/org/pwss/controller/LoginController.java +++ b/src/main/java/org/pwss/controller/LoginController.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import java.util.concurrent.ExecutionException; +import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.pwss.app_settings.AppConfig; import org.pwss.exception.user.CreateUserException; @@ -11,6 +12,7 @@ import org.pwss.navigation.Screen; import org.pwss.service.AuthService; import org.pwss.utils.LoginUtils; +import org.pwss.utils.StringConstants; import org.pwss.view.screen.LoginScreen; import org.slf4j.LoggerFactory; @@ -141,7 +143,19 @@ private void proceedAndValidate() { if (createUserMode) { // Handle user creation logic here and then login - createUserAndLogin(); + int choice = screen.showOptionDialog(JOptionPane.INFORMATION_MESSAGE, + "Welcome to Integrity Hash!\n\n" + + "You are about to create the first user for this application.\n" + + "Please make sure to remember your credentials as they will be required for future logins.\n\n" + + "Do you want to proceed?", + new String[]{StringConstants.GENERIC_YES, StringConstants.GENERIC_NO}, + StringConstants.GENERIC_YES); + + if (choice == 0) { + createUserAndLogin(); + } else { + log.debug("User creation cancelled by user."); + } } else { // Handle normal login logic here performLogin(); From c6d5bcc4289f0fc9ac3fd94c3d857c93eabe3b22 Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 4 Nov 2025 22:25:46 +0100 Subject: [PATCH 27/44] Fix dynamic frame size depending on if createUser mode or regular mode in login screen :) --- .../org/pwss/controller/BaseController.java | 22 ++--- .../org/pwss/controller/HomeController.java | 3 + .../org/pwss/controller/LoginController.java | 3 + .../controller/NewDirectoryController.java | 1 + .../controller/ScanDetailsController.java | 1 + .../pwss/navigation/NavigationHandler.java | 1 + src/main/java/org/pwss/navigation/Screen.java | 2 +- .../java/org/pwss/view/screen/BaseScreen.java | 25 ++++- .../java/org/pwss/view/screen/HomeScreen.java | 2 +- .../org/pwss/view/screen/LoginScreen.form | 98 +++++++++---------- .../org/pwss/view/screen/LoginScreen.java | 62 ++++++------ .../pwss/view/screen/NewDirectoryScreen.java | 4 +- .../pwss/view/screen/ScanDetailsScreen.java | 4 +- 13 files changed, 127 insertions(+), 101 deletions(-) diff --git a/src/main/java/org/pwss/controller/BaseController.java b/src/main/java/org/pwss/controller/BaseController.java index 580d966..2e632b0 100644 --- a/src/main/java/org/pwss/controller/BaseController.java +++ b/src/main/java/org/pwss/controller/BaseController.java @@ -16,12 +16,14 @@ public abstract class BaseController { * Represents the UI component associated with this controller. */ protected Screen screen; - /** * The navigation context for passing data between different parts of the application during navigation. */ private NavigationContext context; - + /** + * Logger instance for logging purposes. + */ + private final org.slf4j.Logger log; /** * Constructs a `BaseController` with the specified view. * Initializes the view and sets up event listeners. @@ -30,12 +32,12 @@ public abstract class BaseController { */ public BaseController(Screen screen) { this.screen = screen; + this.log = org.slf4j.LoggerFactory.getLogger(BaseController.class); // Run onCreate lifecycle method onCreate(); // Initialize event listeners initListeners(); } - /** * Retrieves the current navigation context. * @@ -44,7 +46,6 @@ public BaseController(Screen screen) { protected NavigationContext getContext() { return context; } - /** * Sets the navigation context for the controller. * @@ -53,26 +54,22 @@ protected NavigationContext getContext() { public void setContext(NavigationContext context) { this.context = context; } - /** * Abstract method to initialize event listeners for the Screen. * Subclasses must provide an implementation for this method. */ abstract void initListeners(); - /** * Abstract method to refresh or update the view. * Subclasses must provide an implementation for this method. */ abstract void refreshView(); - /** * Method to reload or refresh data displayed in the view. * Subclasses can override this method to provide specific data reloading logic. - * The default implementation does nothing. */ public void reloadData() { - // Default implementation does nothing + log.debug("reloadData called for {}", screen.getScreenName()); } /** @@ -83,22 +80,19 @@ public void reloadData() { public Screen getScreen() { return screen; } - /** * Method called when the view is shown. * Subclasses can override this method to perform actions when the view becomes visible. - * The default implementation does nothing. */ public void onShow() { - // Default implementation does nothing + log.debug("onShow called for {}", screen.getScreenName()); } /** * Method called when the view is created. * Subclasses can override this method to perform actions during the creation of the view. - * The default implementation does nothing. */ public void onCreate() { - // Default implementation does nothing + log.debug("onCreate called for {}", screen.getScreenName()); } } diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index a7fce75..fec95a6 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -195,6 +195,7 @@ public HomeController(HomeScreen view) { @Override public void onCreate() { + super.onCreate(); // Update theme picker screen.getThemePicker().removeAllItems(); // Populate the combo box with AppTheme values @@ -236,6 +237,7 @@ public Component getListCellRendererComponent(JList list, Object value, @Override public void onShow() { + super.onShow(); fetchDataAndRefreshView(); } @@ -304,6 +306,7 @@ private List safeGetDiffs(long scanId) { @Override public void reloadData() { + super.reloadData(); fetchDataAndRefreshView(); } diff --git a/src/main/java/org/pwss/controller/LoginController.java b/src/main/java/org/pwss/controller/LoginController.java index e4c69bc..1da9ed8 100644 --- a/src/main/java/org/pwss/controller/LoginController.java +++ b/src/main/java/org/pwss/controller/LoginController.java @@ -61,10 +61,13 @@ public LoginController(LoginScreen view) { @Override public void onShow() { + super.onShow(); screen.getUsernameField().setText(""); screen.getPasswordField().setText(""); log.debug("Current LICENSE_KEY: {}", licenseKeySet ? "SET" : "NOT SET"); log.debug("Create User Mode: {}", createUserMode); + // Adjust frame size based on create user mode + screen.getParentFrame().setSize(450, createUserMode ? 300 : 250); refreshView(); } diff --git a/src/main/java/org/pwss/controller/NewDirectoryController.java b/src/main/java/org/pwss/controller/NewDirectoryController.java index 35f3c74..8370474 100644 --- a/src/main/java/org/pwss/controller/NewDirectoryController.java +++ b/src/main/java/org/pwss/controller/NewDirectoryController.java @@ -36,6 +36,7 @@ public NewDirectoryController(NewDirectoryScreen screen) { @Override public void onShow() { + super.onShow(); // Reset selected path when the screen is shown selectedPath = null; refreshView(); diff --git a/src/main/java/org/pwss/controller/ScanDetailsController.java b/src/main/java/org/pwss/controller/ScanDetailsController.java index 1222bfd..a7975fc 100644 --- a/src/main/java/org/pwss/controller/ScanDetailsController.java +++ b/src/main/java/org/pwss/controller/ScanDetailsController.java @@ -66,6 +66,7 @@ public ScanDetailsController(ScanDetailsScreen screen) { @Override public void onShow() { + super.onShow(); if (scanSummaries != null && !scanSummaries.isEmpty()) { // Clear existing data scanSummaries = List.of(); diff --git a/src/main/java/org/pwss/navigation/NavigationHandler.java b/src/main/java/org/pwss/navigation/NavigationHandler.java index 36530a7..661ef7c 100644 --- a/src/main/java/org/pwss/navigation/NavigationHandler.java +++ b/src/main/java/org/pwss/navigation/NavigationHandler.java @@ -50,6 +50,7 @@ public void navigateTo(Screen screen, NavigationContext context) { // Set the navigation context for the controller controller.setContext(context); BaseScreen baseScreen = controller.getScreen(); + baseScreen.setParentFrame(frame); // Ensure the controller reloads its data when navigating to the screen controller.reloadData(); diff --git a/src/main/java/org/pwss/navigation/Screen.java b/src/main/java/org/pwss/navigation/Screen.java index b00ac91..bb9c5b9 100644 --- a/src/main/java/org/pwss/navigation/Screen.java +++ b/src/main/java/org/pwss/navigation/Screen.java @@ -7,7 +7,7 @@ public enum Screen { /** * The login screen where users can authenticate. */ - LOGIN(450, 350), + LOGIN(450, 250), /** * The home screen displayed after successful login. diff --git a/src/main/java/org/pwss/view/screen/BaseScreen.java b/src/main/java/org/pwss/view/screen/BaseScreen.java index 223b4e2..f00a5f7 100644 --- a/src/main/java/org/pwss/view/screen/BaseScreen.java +++ b/src/main/java/org/pwss/view/screen/BaseScreen.java @@ -1,5 +1,6 @@ package org.pwss.view.screen; +import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -10,13 +11,35 @@ * @author PWSS ORG */ public abstract class BaseScreen extends JPanel { + /** + * The parent JFrame of this screen. + */ + private JFrame parentFrame; + + /** + * Set the parent JFrame of this screen. + * + * @param parentFrame The parent JFrame to set. + */ + public void setParentFrame(JFrame parentFrame) { + this.parentFrame = parentFrame; + } + + /** + * Get the parent JFrame of this screen. + * + * @return The parent JFrame. + */ + public JFrame getParentFrame() { + return parentFrame; + } /** * Get the screen name for dialog titles & logging purposes. * * @return The name of the screen. */ - protected abstract String getScreenName(); + public abstract String getScreenName(); /** diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.java b/src/main/java/org/pwss/view/screen/HomeScreen.java index bda50d4..e809c9e 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.java +++ b/src/main/java/org/pwss/view/screen/HomeScreen.java @@ -229,7 +229,7 @@ public class HomeScreen extends BaseScreen { private JCheckBox maxHashExtractionFileSizeUnlimitedCheckbox; @Override - protected String getScreenName() { + public String getScreenName() { return "Home"; } diff --git a/src/main/java/org/pwss/view/screen/LoginScreen.form b/src/main/java/org/pwss/view/screen/LoginScreen.form index 8877f19..16535c0 100644 --- a/src/main/java/org/pwss/view/screen/LoginScreen.form +++ b/src/main/java/org/pwss/view/screen/LoginScreen.form @@ -1,6 +1,6 @@
- + @@ -8,58 +8,23 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + @@ -67,7 +32,7 @@ - + @@ -75,13 +40,31 @@ - + + + + + + + + + + + + + + + + + + + - + @@ -90,7 +73,7 @@ - + @@ -99,20 +82,37 @@ - + - + + + + + + + + + + + - + - - + + + + + + + + + diff --git a/src/main/java/org/pwss/view/screen/LoginScreen.java b/src/main/java/org/pwss/view/screen/LoginScreen.java index 4342a04..c6a5029 100644 --- a/src/main/java/org/pwss/view/screen/LoginScreen.java +++ b/src/main/java/org/pwss/view/screen/LoginScreen.java @@ -35,8 +35,8 @@ public class LoginScreen extends BaseScreen { private JLabel confirmPasswordLabel; @Override - protected String getScreenName() { - return "Authentication"; + public String getScreenName() { + return "Login"; } @Override @@ -178,53 +178,53 @@ public JTextField getLicenseKeyField() { */ private void $$$setupUI$$$() { rootPanel = new JPanel(); - rootPanel.setLayout(new GridLayoutManager(6, 3, new Insets(10, 10, 10, 10), -1, -1)); + rootPanel.setLayout(new GridLayoutManager(7, 3, new Insets(10, 10, 10, 10), -1, -1)); final JPanel panel1 = new JPanel(); - panel1.setLayout(new GridLayoutManager(10, 3, new Insets(0, 0, 0, 0), -1, -1)); - rootPanel.add(panel1, new GridConstraints(0, 0, 6, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); - final JPanel panel2 = new JPanel(); - panel2.setLayout(new GridLayoutManager(1, 2, new Insets(10, 0, 0, 0), -1, -1, true, false)); - panel1.add(panel2, new GridConstraints(9, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); - proceedButton = new JButton(); - proceedButton.setText("Button"); - panel2.add(proceedButton, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - cancelButton = new JButton(); - cancelButton.setText("Cancel"); - panel2.add(cancelButton, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - messageLabel = new JLabel(); - Font messageLabelFont = this.$$$getFont$$$(null, Font.BOLD, 12, messageLabel.getFont()); - if (messageLabelFont != null) messageLabel.setFont(messageLabelFont); - messageLabel.setText(""); - panel1.add(messageLabel, new GridConstraints(0, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.setLayout(new GridLayoutManager(5, 6, new Insets(0, 0, 0, 0), -1, -1)); + rootPanel.add(panel1, new GridConstraints(0, 0, 6, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); passwordField = new JPasswordField(); - panel1.add(passwordField, new GridConstraints(4, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(passwordField, new GridConstraints(2, 1, 1, 5, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); usernameField = new JTextField(); usernameField.setText(""); - panel1.add(usernameField, new GridConstraints(2, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(usernameField, new GridConstraints(1, 1, 1, 5, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); licenseKeyField = new JTextField(); - panel1.add(licenseKeyField, new GridConstraints(8, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); + panel1.add(licenseKeyField, new GridConstraints(4, 1, 1, 5, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); confirmPasswordField = new JPasswordField(); - panel1.add(confirmPasswordField, new GridConstraints(6, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(confirmPasswordField, new GridConstraints(3, 1, 1, 5, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + messageLabel = new JLabel(); + Font messageLabelFont = this.$$$getFont$$$(null, Font.BOLD, 12, messageLabel.getFont()); + if (messageLabelFont != null) messageLabel.setFont(messageLabelFont); + messageLabel.setText("hello"); + panel1.add(messageLabel, new GridConstraints(0, 0, 1, 6, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + usernameLabel = new JLabel(); + Font usernameLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, usernameLabel.getFont()); + if (usernameLabelFont != null) usernameLabel.setFont(usernameLabelFont); + usernameLabel.setText("Username"); + panel1.add(usernameLabel, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); passwordLabel = new JLabel(); Font passwordLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, passwordLabel.getFont()); if (passwordLabelFont != null) passwordLabel.setFont(passwordLabelFont); passwordLabel.setText("Password"); - panel1.add(passwordLabel, new GridConstraints(3, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(passwordLabel, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); confirmPasswordLabel = new JLabel(); Font confirmPasswordLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, confirmPasswordLabel.getFont()); if (confirmPasswordLabelFont != null) confirmPasswordLabel.setFont(confirmPasswordLabelFont); confirmPasswordLabel.setText("Confirm password"); - panel1.add(confirmPasswordLabel, new GridConstraints(5, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(confirmPasswordLabel, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); licenseLabel = new JLabel(); Font licenseLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, licenseLabel.getFont()); if (licenseLabelFont != null) licenseLabel.setFont(licenseLabelFont); licenseLabel.setText("License key"); - panel1.add(licenseLabel, new GridConstraints(7, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - usernameLabel = new JLabel(); - Font usernameLabelFont = this.$$$getFont$$$(null, Font.BOLD, -1, usernameLabel.getFont()); - if (usernameLabelFont != null) usernameLabel.setFont(usernameLabelFont); - usernameLabel.setText("Username"); - panel1.add(usernameLabel, new GridConstraints(1, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(licenseLabel, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JPanel panel2 = new JPanel(); + panel2.setLayout(new GridLayoutManager(1, 2, new Insets(10, 0, 0, 0), -1, -1, true, false)); + rootPanel.add(panel2, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); + proceedButton = new JButton(); + proceedButton.setText("Button"); + panel2.add(proceedButton, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + cancelButton = new JButton(); + cancelButton.setText("Cancel"); + panel2.add(cancelButton, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); } /** diff --git a/src/main/java/org/pwss/view/screen/NewDirectoryScreen.java b/src/main/java/org/pwss/view/screen/NewDirectoryScreen.java index e2309fd..a5df9ee 100644 --- a/src/main/java/org/pwss/view/screen/NewDirectoryScreen.java +++ b/src/main/java/org/pwss/view/screen/NewDirectoryScreen.java @@ -54,8 +54,8 @@ public JLabel getPathLabel() { } @Override - protected String getScreenName() { - return "Create New Directory"; + public String getScreenName() { + return "New monitored directory"; } @Override diff --git a/src/main/java/org/pwss/view/screen/ScanDetailsScreen.java b/src/main/java/org/pwss/view/screen/ScanDetailsScreen.java index 17442f4..9c048b0 100644 --- a/src/main/java/org/pwss/view/screen/ScanDetailsScreen.java +++ b/src/main/java/org/pwss/view/screen/ScanDetailsScreen.java @@ -52,8 +52,8 @@ public class ScanDetailsScreen extends BaseScreen { } @Override - protected String getScreenName() { - return "Scan Details Screen"; + public String getScreenName() { + return "Scan Details"; } /** From 8005213c9a9ed753f3c6d8226d219c9755547885 Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 4 Nov 2025 22:35:45 +0100 Subject: [PATCH 28/44] Some adjustments, now the height is also licenseKey aware --- .../java/org/pwss/controller/LoginController.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/pwss/controller/LoginController.java b/src/main/java/org/pwss/controller/LoginController.java index 1da9ed8..54fbfee 100644 --- a/src/main/java/org/pwss/controller/LoginController.java +++ b/src/main/java/org/pwss/controller/LoginController.java @@ -67,7 +67,9 @@ public void onShow() { log.debug("Current LICENSE_KEY: {}", licenseKeySet ? "SET" : "NOT SET"); log.debug("Create User Mode: {}", createUserMode); // Adjust frame size based on create user mode - screen.getParentFrame().setSize(450, createUserMode ? 300 : 250); + final int frameHeight = createUserMode ? 225 : 200; + final int offset = licenseKeySet ? 0 : 50; + screen.getParentFrame().setSize(450, frameHeight + offset); refreshView(); } @@ -147,10 +149,13 @@ private void proceedAndValidate() { if (createUserMode) { // Handle user creation logic here and then login int choice = screen.showOptionDialog(JOptionPane.INFORMATION_MESSAGE, - "Welcome to Integrity Hash!\n\n" + - "You are about to create the first user for this application.\n" + - "Please make sure to remember your credentials as they will be required for future logins.\n\n" + - "Do you want to proceed?", + """ + Welcome to Integrity Hash! + + You are about to create the first user for this application. + Please make sure to remember your credentials as they will be required for future logins. + + Do you want to proceed?""", new String[]{StringConstants.GENERIC_YES, StringConstants.GENERIC_NO}, StringConstants.GENERIC_YES); From fc19f69e20d8bb903660bfadd591827dd9139827 Mon Sep 17 00:00:00 2001 From: Stefan Date: Sun, 16 Nov 2025 16:28:25 +0100 Subject: [PATCH 29/44] Update user creation text --- src/main/java/org/pwss/controller/LoginController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/pwss/controller/LoginController.java b/src/main/java/org/pwss/controller/LoginController.java index 54fbfee..8080eb7 100644 --- a/src/main/java/org/pwss/controller/LoginController.java +++ b/src/main/java/org/pwss/controller/LoginController.java @@ -152,7 +152,7 @@ private void proceedAndValidate() { """ Welcome to Integrity Hash! - You are about to create the first user for this application. + You are about to create a user for this application. Please make sure to remember your credentials as they will be required for future logins. Do you want to proceed?""", From a16651fbca992743f10100f81ac730525e5bd699 Mon Sep 17 00:00:00 2001 From: Stefan Date: Sun, 16 Nov 2025 16:56:53 +0100 Subject: [PATCH 30/44] Tweak string for update notes --- src/main/java/org/pwss/utils/StringConstants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/pwss/utils/StringConstants.java b/src/main/java/org/pwss/utils/StringConstants.java index f6f087f..7377fa0 100644 --- a/src/main/java/org/pwss/utils/StringConstants.java +++ b/src/main/java/org/pwss/utils/StringConstants.java @@ -63,7 +63,7 @@ public final class StringConstants { // Update note related strings public static final String MON_DIR_POPUP_UPDATE_NOTE = "Update note"; - public static final String MON_DIR_POPUP_UPDATE_NOTE_POPUP_PREFIX = "Update note for:\n"; + public static final String MON_DIR_POPUP_UPDATE_NOTE_POPUP_PREFIX = "Update note for: "; public static final String MON_DIR_POPUP_UPDATE_NOTE_SUCCESS = "Note updated successfully."; public static final String MON_DIR_POPUP_UPDATE_NOTE_ERROR = "Failed to update note."; From ffcf07126852c2a9cfbe3c35a5c198264469c151 Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Sat, 8 Nov 2025 11:45:34 +0100 Subject: [PATCH 31/44] Fix error dialog issue and update dependencies --- pom.xml | 10 +++---- .../org/pwss/controller/HomeController.java | 2 +- .../org/pwss/controller/LoginController.java | 2 +- .../controller/ScanDetailsController.java | 28 ++++++++++++++----- .../java/org/pwss/service/ScanService.java | 2 +- .../org/pwss/view/screen/LoginScreen.java | 1 - 6 files changed, 29 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 7f23fdd..096a5a5 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ com.fasterxml.jackson.core jackson-databind - 2.20.0 + 2.20.1 @@ -78,7 +78,7 @@ org.junit.jupiter junit-jupiter-api - 6.0.0 + 6.0.1 test @@ -94,7 +94,7 @@ com.formdev flatlaf - 3.6.1 + 3.6.2 @@ -106,7 +106,7 @@ org.codehaus.mojo exec-maven-plugin - 3.5.1 + 3.6.2 ${project.mainClass} @@ -117,7 +117,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.0 + 3.14.1 ${maven.compiler.source} ${maven.compiler.target} diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index fec95a6..8abe0b4 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -679,7 +679,7 @@ public void performStartScan(boolean singleDirectory) { if (baseLineScan) { screen.showSuccess(StringConstants.SCAN_STARTED_BASELINE_SUCCESS); } else { - screen.showSuccess(StringConstants.SCAN_STARTED_SUCCESS); + log.info(StringConstants.SCAN_STARTED_SUCCESS); } startPollingScanLiveFeed(singleDirectory, scanningDirs); } else { diff --git a/src/main/java/org/pwss/controller/LoginController.java b/src/main/java/org/pwss/controller/LoginController.java index 8080eb7..2e8e254 100644 --- a/src/main/java/org/pwss/controller/LoginController.java +++ b/src/main/java/org/pwss/controller/LoginController.java @@ -253,7 +253,7 @@ private void performLogin() { if (createUserMode) { screen.showInfo("User created and logged in successfully!"); } else { - screen.showInfo("Logged in successfully!"); + log.info("Logged in successfully!"); } AppConfig.setLicenseKey(licenseKey); NavigationEvents.navigateTo(Screen.HOME); diff --git a/src/main/java/org/pwss/controller/ScanDetailsController.java b/src/main/java/org/pwss/controller/ScanDetailsController.java index a7975fc..af4a273 100644 --- a/src/main/java/org/pwss/controller/ScanDetailsController.java +++ b/src/main/java/org/pwss/controller/ScanDetailsController.java @@ -19,12 +19,18 @@ import org.pwss.utils.ReportUtils; import org.pwss.utils.StringConstants; import org.pwss.view.screen.ScanDetailsScreen; +import org.slf4j.LoggerFactory; /** * Controller class that handles operations related to the scan details screen. */ public class ScanDetailsController extends BaseController implements CellButtonListener { + /** + * Logger for logging messages within this controller. + */ + private final org.slf4j.Logger log = LoggerFactory.getLogger(LoginController.class); + /** * Service responsible for managing scan summaries. */ @@ -140,7 +146,8 @@ void refreshView() { screen.getDiffTable().setModel(diffTableModel); screen.getDiffTable().getColumn(DiffTableModel.columns[3]).setCellRenderer(new ButtonRenderer()); - screen.getDiffTable().getColumn(DiffTableModel.columns[3]).setCellEditor(new ButtonEditor("\uD83D\uDCE5", this)); + screen.getDiffTable().getColumn(DiffTableModel.columns[3]) + .setCellEditor(new ButtonEditor("\uD83D\uDCE5", this)); } @Override @@ -152,21 +159,28 @@ public void onCellButtonClicked(int row, int column) { int choice = screen.showOptionDialog( JOptionPane.WARNING_MESSAGE, OSUtils.getQuarantineWarningMessage(), - new String[]{StringConstants.GENERIC_YES, StringConstants.GENERIC_NO}, - StringConstants.GENERIC_NO - ); + new String[] { StringConstants.GENERIC_YES, StringConstants.GENERIC_NO }, + StringConstants.GENERIC_NO); if (choice == 0) { try { boolean success = fileService.quarantineFile(d.integrityFail().file().id()); if (success) { - screen.showInfo("File quarantined successfully."); + log.info("File quarantined successfully."); screen.getDiffTable().getColumn(d).setCellRenderer(new ButtonRenderer()); screen.getDiffTable().getColumn(d).setCellEditor(new ButtonEditor("🗿", this)); } else { screen.showError("Failed to quarantine the file."); } - } catch (Exception e) { - screen.showError(e.getMessage()); + + } + + catch (IllegalArgumentException iae) { + log.debug("Illegal argument exception {}",iae); + } + + catch (Exception e) { + log.debug("Exception thrown {} ", e); + log.error("Error code 455 : {}",e.getMessage()); } } }); diff --git a/src/main/java/org/pwss/service/ScanService.java b/src/main/java/org/pwss/service/ScanService.java index 7220406..33698db 100644 --- a/src/main/java/org/pwss/service/ScanService.java +++ b/src/main/java/org/pwss/service/ScanService.java @@ -208,7 +208,7 @@ public List getDiffs(long scanId, long limit, String sortField, boolean as case 200 -> List.of(objectMapper.readValue(response.body(), Diff[].class)); case 401 -> throw new GetScanDiffsException("Failed to fetch scan diffs: User not authorized to perform this action."); - case 500 -> throw new GetScanDiffsException("Failed to fetch scan diffs: Server error"); + case 500 -> throw new GetScanDiffsException("Failed to fetch scan diffs"); default -> Collections.emptyList(); }; } diff --git a/src/main/java/org/pwss/view/screen/LoginScreen.java b/src/main/java/org/pwss/view/screen/LoginScreen.java index c6a5029..f2e80e7 100644 --- a/src/main/java/org/pwss/view/screen/LoginScreen.java +++ b/src/main/java/org/pwss/view/screen/LoginScreen.java @@ -2,7 +2,6 @@ import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.core.GridLayoutManager; -import com.intellij.uiDesigner.core.Spacer; import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; From 308b8510651656629934b75218055842b5e55d88 Mon Sep 17 00:00:00 2001 From: Stefan Date: Sun, 16 Nov 2025 17:03:40 +0100 Subject: [PATCH 32/44] Bump logback-classic -> 1.5.21 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 096a5a5..6ca990d 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ ch.qos.logback logback-classic - 1.5.20 + 1.5.21 From a9500ba7b69db235334f86a286830557b66fed94 Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 17 Nov 2025 07:19:27 +0100 Subject: [PATCH 33/44] Refine welcome message for clarity and update note formatting --- .../org/pwss/controller/LoginController.java | 18 +-- .../java/org/pwss/utils/StringConstants.java | 2 +- src/main/java/org/pwss/utils/StringUtils.java | 115 ++++++++++++++++++ .../MonitoredDirectoryPopupFactory.java | 42 ++++--- 4 files changed, 148 insertions(+), 29 deletions(-) create mode 100644 src/main/java/org/pwss/utils/StringUtils.java diff --git a/src/main/java/org/pwss/controller/LoginController.java b/src/main/java/org/pwss/controller/LoginController.java index 2e8e254..ff36bc5 100644 --- a/src/main/java/org/pwss/controller/LoginController.java +++ b/src/main/java/org/pwss/controller/LoginController.java @@ -16,7 +16,6 @@ import org.pwss.view.screen.LoginScreen; import org.slf4j.LoggerFactory; - import static org.pwss.app_settings.AppConfig.LICENSE_KEY; /** @@ -151,12 +150,12 @@ private void proceedAndValidate() { int choice = screen.showOptionDialog(JOptionPane.INFORMATION_MESSAGE, """ Welcome to Integrity Hash! - - You are about to create a user for this application. - Please make sure to remember your credentials as they will be required for future logins. - - Do you want to proceed?""", - new String[]{StringConstants.GENERIC_YES, StringConstants.GENERIC_NO}, + + You are about to set up access to this application. Please make sure to remember your credentials as they will be required for future logins. + + Do you want to proceed? + """, + new String[] { StringConstants.GENERIC_YES, StringConstants.GENERIC_NO }, StringConstants.GENERIC_YES); if (choice == 0) { @@ -181,7 +180,8 @@ private boolean validateInput() { String confirmPassword = screen.getConfirmPassword(); String licenseKey = licenseKeySet ? LICENSE_KEY : screen.getLicenseKey(); - LoginUtils.LoginValidationResult result = LoginUtils.validateInput(username, password, confirmPassword, licenseKey, createUserMode); + LoginUtils.LoginValidationResult result = LoginUtils.validateInput(username, password, confirmPassword, + licenseKey, createUserMode); if (!result.isValid()) { screen.showError(LoginUtils.formatErrors(result.errors())); } @@ -253,7 +253,7 @@ private void performLogin() { if (createUserMode) { screen.showInfo("User created and logged in successfully!"); } else { - log.info("Logged in successfully!"); + log.info("Logged in successfully!"); } AppConfig.setLicenseKey(licenseKey); NavigationEvents.navigateTo(Screen.HOME); diff --git a/src/main/java/org/pwss/utils/StringConstants.java b/src/main/java/org/pwss/utils/StringConstants.java index 7377fa0..d4a74be 100644 --- a/src/main/java/org/pwss/utils/StringConstants.java +++ b/src/main/java/org/pwss/utils/StringConstants.java @@ -63,7 +63,7 @@ public final class StringConstants { // Update note related strings public static final String MON_DIR_POPUP_UPDATE_NOTE = "Update note"; - public static final String MON_DIR_POPUP_UPDATE_NOTE_POPUP_PREFIX = "Update note for: "; + public static final String MON_DIR_POPUP_UPDATE_NOTE_POPUP_PREFIX = "Update Note -"; public static final String MON_DIR_POPUP_UPDATE_NOTE_SUCCESS = "Note updated successfully."; public static final String MON_DIR_POPUP_UPDATE_NOTE_ERROR = "Failed to update note."; diff --git a/src/main/java/org/pwss/utils/StringUtils.java b/src/main/java/org/pwss/utils/StringUtils.java new file mode 100644 index 0000000..de26a0c --- /dev/null +++ b/src/main/java/org/pwss/utils/StringUtils.java @@ -0,0 +1,115 @@ +package org.pwss.utils; + +/** + * A utility class for string manipulation operations. + * This class contains static methods to perform various string manipulations. + */ +public final class StringUtils { + + /** + * + * Private constructor to prevent instantiation + **/ + private StringUtils() { + } + + /** + * Adds a space character at the end of the given string. + * + * @param str The input string. If it is null, null is returned. + * @return A new string with a space appended to the end of the input string, + * or null if the input was null. + */ + public static String addSpace(String str) { + if (str == null) + return null; + return str + " "; + } + + /** + * Adds a space character at the beginning of the given string. + * + * @param str The input string. If it is null, null is returned. + * @return A new string with a space prepended to the beginning of the input + * string, + * or null if the input was null. + */ + public static String prependSpace(String str) { + if (str == null) + return null; + return " " + str; + } + + /** + * Pads the given string on the right with spaces until it reaches the specified + * length. + * + * @param str The input string. If it is null, null is returned. + * @param length The desired length of the resulting string. If the input + * string's + * length is already greater than or equal to this value, the + * original + * string will be returned unchanged. + * @return A new string with spaces appended on the right until it reaches the + * specified + * length, or null if the input was null. + */ + public static String padRight(String str, int length) { + if (str == null) + return null; + if (str.length() >= length) + return str; + StringBuilder sb = new StringBuilder(str); + while (sb.length() < length) { + sb.append(' '); + } + return sb.toString(); + } + + /** + * Pads the given string on the left with spaces until it reaches the specified + * length. + * + * @param str The input string. If it is null, null is returned. + * @param length The desired length of the resulting string. If the input + * string's + * length is already greater than or equal to this value, the + * original + * string will be returned unchanged. + * @return A new string with spaces prepended on the left until it reaches the + * specified + * length, or null if the input was null. + */ + public static String padLeft(String str, int length) { + if (str == null) + return null; + if (str.length() >= length) + return str; + StringBuilder sb = new StringBuilder(); + while (sb.length() < length - str.length()) { + sb.append(' '); + } + sb.append(str); + return sb.toString(); + } + + /** + * Capitalizes the first letter of the given string. + * + * @param str The input string. If it is null or empty, the original string will + * be returned. + * @return A new string with the first letter capitalized, or the original + * string if + * it was null or empty. + */ + public static String capitalizeFirstLetter(String str) { + if (isEmpty(str)) + return str; + return str.substring(0, 1).toUpperCase() + str.substring(1); + } + + // Assuming isEmpty method exists as referred in the code + private static boolean isEmpty(String str) { + return str == null || str.isEmpty(); + } +} \ No newline at end of file diff --git a/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java b/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java index 8447121..b43e090 100644 --- a/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java +++ b/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java @@ -19,11 +19,14 @@ import org.pwss.model.request.notes.RestoreNoteType; import org.pwss.model.table.MonitoredDirectoryTableModel; import org.pwss.utils.StringConstants; +import org.pwss.utils.StringUtils; import org.pwss.view.popup_menu.listener.MonitoredDirectoryPopupListener; /** - * Factory class to create a context menu (popup menu) for monitored directories in a JTable. - * The menu provides options to scan the directory, reset its baseline, and edit its details. + * Factory class to create a context menu (popup menu) for monitored directories + * in a JTable. + * The menu provides options to scan the directory, reset its baseline, and edit + * its details. */ public class MonitoredDirectoryPopupFactory { private final MonitoredDirectoryPopupListener listener; @@ -39,11 +42,13 @@ public JPopupMenu create(JTable table, int viewRow) { MonitoredDirectoryTableModel model = (MonitoredDirectoryTableModel) table.getModel(); Optional dirOpt = model.getDirectoryAt(modelRow); - if (dirOpt.isEmpty()) return menu; + if (dirOpt.isEmpty()) + return menu; MonitoredDirectory dir = dirOpt.get(); // Scan this directory - JMenuItem scanItem = new JMenuItem(dir.baselineEstablished() ? StringConstants.MON_DIR_POPUP_START_SCAN : StringConstants.MON_DIR_POPUP_ESTABLISH_BASELINE); + JMenuItem scanItem = new JMenuItem(dir.baselineEstablished() ? StringConstants.MON_DIR_POPUP_START_SCAN + : StringConstants.MON_DIR_POPUP_ESTABLISH_BASELINE); scanItem.addActionListener(e -> listener.onStartScan()); // Reset baseline @@ -87,19 +92,23 @@ public JPopupMenu create(JTable table, int viewRow) { * @return the JMenuItem for toggling the active status */ private JMenuItem getToggleActiveMenuItem(MonitoredDirectory dir) { - JMenuItem editItem = new JMenuItem(dir.isActive() ? StringConstants.MON_DIR_TOGGLE_ACTIVE_DISABLE : StringConstants.MON_DIR_TOGGLE_ACTIVE_ENABLE); + JMenuItem editItem = new JMenuItem(dir.isActive() ? StringConstants.MON_DIR_TOGGLE_ACTIVE_DISABLE + : StringConstants.MON_DIR_TOGGLE_ACTIVE_ENABLE); editItem.addActionListener(e -> listener.onToggleActiveStatus(dir)); return editItem; } /** - * Creates a menu item for toggling the inclusion of subdirectories in a monitored directory. + * Creates a menu item for toggling the inclusion of subdirectories in a + * monitored directory. * * @param dir the monitored directory for which to create the menu item * @return the JMenuItem for toggling the inclusion of subdirectories */ private JMenuItem getToggleIncludeSubDirsItem(MonitoredDirectory dir) { - JMenuItem editItem = new JMenuItem(dir.includeSubdirectories() ? StringConstants.MON_DIR_TOGGLE_INCLUDE_SUBDIR_DISABLE : StringConstants.MON_DIR_TOGGLE_INCLUDE_SUBDIR_ENABLE); + JMenuItem editItem = new JMenuItem( + dir.includeSubdirectories() ? StringConstants.MON_DIR_TOGGLE_INCLUDE_SUBDIR_DISABLE + : StringConstants.MON_DIR_TOGGLE_INCLUDE_SUBDIR_ENABLE); editItem.addActionListener(e -> listener.onToggleIncludeSubdirectories(dir)); return editItem; } @@ -121,8 +130,7 @@ private JMenuItem getShowNoteItem(MonitoredDirectory dir) { listener.getParentComponent(), note, StringConstants.MON_DIR_POPUP_SHOW_NOTE, - JOptionPane.INFORMATION_MESSAGE - ); + JOptionPane.INFORMATION_MESSAGE); }); return showNoteItem; } @@ -137,7 +145,8 @@ private JMenuItem getUpdateNoteItem(MonitoredDirectory dir) { JMenuItem updateNoteItem = new JMenuItem(StringConstants.MON_DIR_POPUP_UPDATE_NOTE); updateNoteItem.addActionListener(e -> { // Label for directory path - JLabel label = new JLabel(StringConstants.MON_DIR_POPUP_UPDATE_NOTE_POPUP_PREFIX + dir.path()); + JLabel label = new JLabel( + StringConstants.MON_DIR_POPUP_UPDATE_NOTE_POPUP_PREFIX + StringUtils.prependSpace(dir.path())); label.setAlignmentX(Component.LEFT_ALIGNMENT); // Text area for note input @@ -164,8 +173,7 @@ private JMenuItem getUpdateNoteItem(MonitoredDirectory dir) { panel, StringConstants.MON_DIR_POPUP_UPDATE_NOTE, JOptionPane.OK_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE - ); + JOptionPane.PLAIN_MESSAGE); // User cancelled if (result != JOptionPane.OK_OPTION) @@ -200,8 +208,7 @@ private JMenuItem getRestoreNoteItem(MonitoredDirectory dir) { listener.getParentComponent(), StringConstants.MON_DIR_POPUP_RESTORE_NO_NOTE_FALLBACK + dir.path(), StringConstants.MON_DIR_POPUP_RESTORE_NOTE, - JOptionPane.INFORMATION_MESSAGE - ); + JOptionPane.INFORMATION_MESSAGE); return; } @@ -241,8 +248,7 @@ private JMenuItem getRestoreNoteItem(MonitoredDirectory dir) { JOptionPane.PLAIN_MESSAGE, null, options.toArray(), - options.get(0) - ); + options.get(0)); // Handle result if (choice == JOptionPane.CLOSED_OPTION || choice == options.size() - 1) { @@ -273,8 +279,7 @@ private JMenuItem getResetBaselineItem(MonitoredDirectory dir) { listener.getParentComponent(), StringConstants.MON_DIR_POPUP_RESET_BASELINE_POPUP_MESSAGE + dir.path(), StringConstants.MON_DIR_POPUP_RESET_BASELINE_POPUP_TITLE, - JOptionPane.WARNING_MESSAGE - ); + JOptionPane.WARNING_MESSAGE); try { // User cancelled @@ -290,4 +295,3 @@ private JMenuItem getResetBaselineItem(MonitoredDirectory dir) { return resetBaselineItem; } } - From e09cdc51af1ed4099403829487206a547f43000a Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 17 Nov 2025 07:29:24 +0100 Subject: [PATCH 34/44] Update Maven repository reference comments in pom.xml --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 6ca990d..d3013a9 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ - + com.fasterxml.jackson.core jackson-databind @@ -90,7 +90,7 @@ - + com.formdev flatlaf @@ -103,7 +103,7 @@ - + org.codehaus.mojo exec-maven-plugin 3.6.2 @@ -114,7 +114,7 @@ - + org.apache.maven.plugins maven-compiler-plugin 3.14.1 @@ -126,7 +126,7 @@ - + org.apache.maven.plugins maven-assembly-plugin 3.7.1 From 98d40578a331a62f69673a01dba750cf47de3d15 Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 17 Nov 2025 07:34:39 +0100 Subject: [PATCH 35/44] Update Maven repository reference comments in pom.xml nr 2 Make comment link ctrl + click able :) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d3013a9..6a6cbf1 100644 --- a/pom.xml +++ b/pom.xml @@ -126,7 +126,7 @@ - + org.apache.maven.plugins maven-assembly-plugin 3.7.1 From 2299bfae9b0950a273ad28a312ba4fe2a84d9b50 Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 17 Nov 2025 09:11:05 +0100 Subject: [PATCH 36/44] Clean up --- src/main/java/org/pwss/utils/StringUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/pwss/utils/StringUtils.java b/src/main/java/org/pwss/utils/StringUtils.java index de26a0c..00617db 100644 --- a/src/main/java/org/pwss/utils/StringUtils.java +++ b/src/main/java/org/pwss/utils/StringUtils.java @@ -108,7 +108,6 @@ public static String capitalizeFirstLetter(String str) { return str.substring(0, 1).toUpperCase() + str.substring(1); } - // Assuming isEmpty method exists as referred in the code private static boolean isEmpty(String str) { return str == null || str.isEmpty(); } From aee252b1539c33c62da8a4cb340ba28d0104b741 Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 17 Nov 2025 09:16:09 +0100 Subject: [PATCH 37/44] Added Java Doc --- src/main/java/org/pwss/utils/StringUtils.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/pwss/utils/StringUtils.java b/src/main/java/org/pwss/utils/StringUtils.java index 00617db..beff46a 100644 --- a/src/main/java/org/pwss/utils/StringUtils.java +++ b/src/main/java/org/pwss/utils/StringUtils.java @@ -108,6 +108,12 @@ public static String capitalizeFirstLetter(String str) { return str.substring(0, 1).toUpperCase() + str.substring(1); } + /** + * Checks if a given string is empty or null. + * + * @param str the string to check, can be null + * @return true if the string is null or has length 0, false otherwise + */ private static boolean isEmpty(String str) { return str == null || str.isEmpty(); } From bebb98e23a265df0f0eeeef5bbf0b381bb7aedef Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 17 Nov 2025 13:37:28 +0100 Subject: [PATCH 38/44] MDs that does not exists wont be shown to the enduser Also change utils -> util --- .../pwss/FileIntegrityScannerFrontend.java | 4 +- .../org/pwss/controller/HomeController.java | 46 ++++++++--------- .../org/pwss/controller/LoginController.java | 8 +-- .../controller/NewDirectoryController.java | 6 +-- .../controller/ScanDetailsController.java | 12 ++--- .../pwss/model/entity/MonitoredDirectory.java | 51 +++++++++++++++++-- .../table/MonitoredDirectoryTableModel.java | 13 +++-- .../model/table/QuarantineTableModel.java | 4 +- .../org/pwss/{utils => util}/AppTheme.java | 2 +- .../ConversionUtil.java} | 4 +- .../ErrorUtils.java => util/ErrorUtil.java} | 4 +- .../LiveFeedUtil.java} | 4 +- .../LoginUtils.java => util/LoginUtil.java} | 6 +-- .../MonitoredDirectoryUtil.java} | 32 ++++++++++-- .../{utils/OSUtils.java => util/OSUtil.java} | 6 +-- .../ReportUtils.java => util/ReportUtil.java} | 4 +- .../pwss/{utils => util}/StringConstants.java | 2 +- .../StringUtils.java => util/StringUtil.java} | 6 +-- .../MonitoredDirectoryPopupFactory.java | 6 +-- .../MonitoredDirectoryPopupListenerImpl.java | 2 +- .../java/org/pwss/view/screen/HomeScreen.java | 2 +- .../LoginUtilTest.java} | 31 ++++++----- .../OSUtilsTest.java => util/OSUtilTest.java} | 22 ++++---- 23 files changed, 174 insertions(+), 103 deletions(-) rename src/main/java/org/pwss/{utils => util}/AppTheme.java (95%) rename src/main/java/org/pwss/{utils/ConversionUtils.java => util/ConversionUtil.java} (98%) rename src/main/java/org/pwss/{utils/ErrorUtils.java => util/ErrorUtil.java} (92%) rename src/main/java/org/pwss/{utils/LiveFeedUtils.java => util/LiveFeedUtil.java} (97%) rename src/main/java/org/pwss/{utils/LoginUtils.java => util/LoginUtil.java} (97%) rename src/main/java/org/pwss/{utils/MonitoredDirectoryUtils.java => util/MonitoredDirectoryUtil.java} (78%) rename src/main/java/org/pwss/{utils/OSUtils.java => util/OSUtil.java} (98%) rename src/main/java/org/pwss/{utils/ReportUtils.java => util/ReportUtil.java} (98%) rename src/main/java/org/pwss/{utils => util}/StringConstants.java (99%) rename src/main/java/org/pwss/{utils/StringUtils.java => util/StringUtil.java} (97%) rename src/test/java/org/pwss/{utils/LoginUtilsTest.java => util/LoginUtilTest.java} (67%) rename src/test/java/org/pwss/{utils/OSUtilsTest.java => util/OSUtilTest.java} (75%) diff --git a/src/main/java/org/pwss/FileIntegrityScannerFrontend.java b/src/main/java/org/pwss/FileIntegrityScannerFrontend.java index e8deb54..72fd5d3 100644 --- a/src/main/java/org/pwss/FileIntegrityScannerFrontend.java +++ b/src/main/java/org/pwss/FileIntegrityScannerFrontend.java @@ -16,7 +16,7 @@ import org.pwss.navigation.NavigationEvents; import org.pwss.navigation.NavigationHandler; import org.pwss.navigation.Screen; -import org.pwss.utils.OSUtils; +import org.pwss.util.OSUtil; import org.pwss.view.screen.splash_screen.FileIntegrityScannerSplashScreen; import org.slf4j.LoggerFactory; @@ -40,7 +40,7 @@ class FileIntegrityScannerFrontend { final static void StartApplication() throws FailedToLaunchAppException { final org.slf4j.Logger log = LoggerFactory.getLogger(FileIntegrityScannerFrontend.class); log.debug("Starting File-Integrity Scanner Frontend Application"); - log.debug("OS Name: {}", OSUtils.getOSName()); + log.debug("OS Name: {}", OSUtil.getOSName()); try { if (AppConfig.USE_SPLASH_SCREEN) { FileIntegrityScannerSplashScreen.showSplash(); diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index 8abe0b4..174c3df 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -59,14 +59,14 @@ import org.pwss.service.NoteService; import org.pwss.service.ScanService; import org.pwss.service.ScanSummaryService; -import org.pwss.utils.AppTheme; -import org.pwss.utils.ConversionUtils; -import org.pwss.utils.ErrorUtils; -import org.pwss.utils.LiveFeedUtils; -import org.pwss.utils.MonitoredDirectoryUtils; -import org.pwss.utils.OSUtils; -import org.pwss.utils.ReportUtils; -import org.pwss.utils.StringConstants; +import org.pwss.util.AppTheme; +import org.pwss.util.ConversionUtil; +import org.pwss.util.ErrorUtil; +import org.pwss.util.LiveFeedUtil; +import org.pwss.util.MonitoredDirectoryUtil; +import org.pwss.util.OSUtil; +import org.pwss.util.ReportUtil; +import org.pwss.util.StringConstants; import org.pwss.view.popup_menu.MonitoredDirectoryPopupFactory; import org.pwss.view.popup_menu.listener.MonitoredDirectoryPopupListenerImpl; import org.pwss.view.screen.HomeScreen; @@ -76,7 +76,6 @@ import static org.pwss.app_settings.AppConfig.MAX_HASH_EXTRACTION_FILE_SIZE; import static org.pwss.app_settings.AppConfig.USE_SPLASH_SCREEN; -// TODO: NEEDS REFACTORING - VERY LARGE CLASS public final class HomeController extends BaseController { /** @@ -250,7 +249,8 @@ void fetchDataAndRefreshView() { try { // Fetch all monitored directories for display in the monitored directories // table - allMonitoredDirectories = monitoredDirectoryService.getAllDirectories(); + allMonitoredDirectories = MonitoredDirectoryUtil + .filterMonitoredDirectoriesOnConfirmedPath(monitoredDirectoryService.getAllDirectories()); // Only fetch diffs if there are active monitored directories present final long activeDirCount = allMonitoredDirectories.stream().filter(MonitoredDirectory::isActive).count(); @@ -371,7 +371,7 @@ public void mouseClicked(MouseEvent e) { DiffTableModel model = (DiffTableModel) screen.getDiffTable().getModel(); Optional diff = model.getDiffAt(modelRow); - diff.ifPresent(d -> screen.getDiffDetails().setText(ReportUtils.formatDiff(d))); + diff.ifPresent(d -> screen.getDiffDetails().setText(ReportUtil.formatDiff(d))); } else { screen.getDiffDetails().setText(""); } @@ -395,7 +395,7 @@ public void mouseClicked(MouseEvent e) { int selectedRow = screen.getFileScanSummaryTable().getSelectedRow(); if (selectedRow >= 0 && selectedRow < fileSummaries.size()) { ScanSummary selectedSummary = fileSummaries.get(selectedRow); - screen.getScanSummaryDetails().setText(ReportUtils.formatSummary(selectedSummary)); + screen.getScanSummaryDetails().setText(ReportUtil.formatSummary(selectedSummary)); } else { screen.getScanSummaryDetails().setText(""); } @@ -426,12 +426,12 @@ public void mouseClicked(MouseEvent e) { screen.getMaxHashExtractionFileSizeSlider().addChangeListener(l -> { long valueMegabytes = screen.getMaxHashExtractionFileSizeSlider().getValue(); log.debug("Setting max hash extraction file size to {} MB", valueMegabytes); - long valueBytes = ConversionUtils.megabytesToBytes(valueMegabytes); + long valueBytes = ConversionUtil.megabytesToBytes(valueMegabytes); // Set App config value to size in bytes if (AppConfig.setMaxHashExtractionFileSize(valueBytes)) { screen.getMaxHashExtractionFileSizeValueLabel().setText(valueMegabytes + " MB"); - maxFileSizeForHashExtraction = ConversionUtils.megabytesToBytes(valueMegabytes); + maxFileSizeForHashExtraction = ConversionUtil.megabytesToBytes(valueMegabytes); } else { log.error("Failed to update max hash extraction file size in app config."); } @@ -452,7 +452,7 @@ public void mouseClicked(MouseEvent e) { // If unchecked set the size to the current slider value long sliderValueMegabytes = screen.getMaxHashExtractionFileSizeSlider().getValue(); log.debug("Setting max hash extraction file size to {} MB", sliderValueMegabytes); - long sliderValueBytes = ConversionUtils.megabytesToBytes(sliderValueMegabytes); + long sliderValueBytes = ConversionUtil.megabytesToBytes(sliderValueMegabytes); if (AppConfig.setMaxHashExtractionFileSize(sliderValueBytes)) { maxFileSizeForHashExtraction = sliderValueBytes; screen.getMaxHashExtractionFileSizeValueLabel().setText(sliderValueMegabytes + " MB"); @@ -469,7 +469,7 @@ protected void refreshView() { // Update UI components based on the current state screen.getScanButton().setText(scanRunning ? StringConstants.SCAN_STOP : StringConstants.SCAN_FULL); - String notifications = MonitoredDirectoryUtils + String notifications = MonitoredDirectoryUtil .getMonitoredDirectoryNotificationMessage(allMonitoredDirectories); boolean hasNotifications = !notifications.isEmpty(); @@ -515,7 +515,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i if (!dir.baselineEstablished()) { setForeground(Color.YELLOW); setToolTipText(StringConstants.TOOLTIP_BASELINE_NOT_ESTABLISHED); - } else if (MonitoredDirectoryUtils.isScanOlderThanAWeek(dir)) { + } else if (MonitoredDirectoryUtil.isScanOlderThanAWeek(dir)) { setForeground(Color.ORANGE); setToolTipText(StringConstants.TOOLTIP_OLD_SCAN); } else { @@ -543,7 +543,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i diff.ifPresent(d -> { int choice = screen.showOptionDialog( JOptionPane.WARNING_MESSAGE, - OSUtils.getQuarantineWarningMessage(), + OSUtil.getQuarantineWarningMessage(), new String[] { StringConstants.GENERIC_YES, StringConstants.GENERIC_NO }, StringConstants.GENERIC_NO); if (choice == 0) { @@ -607,7 +607,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i screen.getMaxHashExtractionFileSizeSlider().setEnabled(true); final int maxSliderValueMegabytes = Math - .toIntExact(ConversionUtils.bytesToMegabytes(maxFileSizeForHashExtraction)); + .toIntExact(ConversionUtil.bytesToMegabytes(maxFileSizeForHashExtraction)); screen.getMaxHashExtractionFileSizeSlider().setValue(maxSliderValueMegabytes); screen.getMaxHashExtractionFileSizeValueLabel().setText(maxSliderValueMegabytes + " MB"); } else { @@ -691,7 +691,7 @@ public void performStartScan(boolean singleDirectory) { log.debug(StringConstants.SCAN_START_ERROR, e); log.error(StringConstants.SCAN_START_ERROR + " {}", e.getMessage()); SwingUtilities.invokeLater(() -> screen - .showError(ErrorUtils.formatErrorMessage(StringConstants.SCAN_START_ERROR, e.getMessage()))); + .showError(ErrorUtil.formatErrorMessage(StringConstants.SCAN_START_ERROR, e.getMessage()))); } } @@ -830,12 +830,12 @@ private void startPollingScanLiveFeed(boolean singleDirectory, List= 0 && selectedRow < scanSummaries.size()) { ScanSummary selectedSummary = scanSummaries.get(selectedRow); - screen.getScanSummaryDetails().setText(ReportUtils.formatSummary(selectedSummary)); + screen.getScanSummaryDetails().setText(ReportUtil.formatSummary(selectedSummary)); } else { screen.getScanSummaryDetails().setText(""); screen.getDiffDetails().setText(""); @@ -125,7 +125,7 @@ void initListeners() { int selectedRow = screen.getDiffTable().getSelectedRow(); if (selectedRow >= 0 && selectedRow < diffs.size()) { Diff selectedDiff = diffs.get(selectedRow); - screen.getDiffDetails().setText(ReportUtils.formatDiff(selectedDiff)); + screen.getDiffDetails().setText(ReportUtil.formatDiff(selectedDiff)); } else { screen.getDiffDetails().setText(""); } @@ -158,7 +158,7 @@ public void onCellButtonClicked(int row, int column) { diff.ifPresent(d -> { int choice = screen.showOptionDialog( JOptionPane.WARNING_MESSAGE, - OSUtils.getQuarantineWarningMessage(), + OSUtil.getQuarantineWarningMessage(), new String[] { StringConstants.GENERIC_YES, StringConstants.GENERIC_NO }, StringConstants.GENERIC_NO); if (choice == 0) { diff --git a/src/main/java/org/pwss/model/entity/MonitoredDirectory.java b/src/main/java/org/pwss/model/entity/MonitoredDirectory.java index 6ff361e..387a5a1 100644 --- a/src/main/java/org/pwss/model/entity/MonitoredDirectory.java +++ b/src/main/java/org/pwss/model/entity/MonitoredDirectory.java @@ -3,6 +3,51 @@ import java.util.Date; -public record MonitoredDirectory(long id, String path, boolean isActive, Time addedAt, Date lastScanned, Notes notes, - boolean baselineEstablished, boolean includeSubdirectories) { -} +/** + * A record class representing a monitored directory. + * This class stores information about directories that are being monitored, + * including their ID, path, active status, timestamps, and other relevant details. + */ +public record MonitoredDirectory( + /** + * The unique identifier for this monitored directory. + */ + long id, + + /** + * The file system path of the monitored directory. + */ + String path, + + /** + * A flag indicating whether the directory is currently active (being monitored). + */ + boolean isActive, + + /** + * The time when this directory was added to monitoring. + */ + Time addedAt, + + /** + * The date and time when this directory was last scanned. + */ + Date lastScanned, + + /** + * Additional notes or comments about this monitored directory. + */ + Notes notes, + + /** + * A flag indicating whether a baseline has been established for this directory. + * This might be used to determine if the initial state of the directory has been recorded. + */ + boolean baselineEstablished, + + /** + * A flag indicating whether subdirectories should also be monitored + * when monitoring this directory. + */ + boolean includeSubdirectories) { +} \ No newline at end of file diff --git a/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java b/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java index 8f2d2c2..3bfd296 100644 --- a/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java +++ b/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java @@ -1,19 +1,22 @@ package org.pwss.model.table; import java.util.Date; + import java.util.List; import java.util.Optional; import javax.swing.table.AbstractTableModel; import org.pwss.model.entity.MonitoredDirectory; +import org.pwss.util.MonitoredDirectoryUtil; public class MonitoredDirectoryTableModel extends AbstractTableModel { private final List directories; private final String[] columnNames = { - "\uD83D\uDCC1 Path", "\uD83D\uDCDD Note", "\uD83D\uDD59 Last Scanned", "\u2693 Baseline Established", "\uD83D\uDCC2 Include Subdirectories", "\uD83D\uDD0C Active" + "\uD83D\uDCC1 Path", "\uD83D\uDCDD Note", "\uD83D\uDD59 Last Scanned", "\u2693 Baseline Established", + "\uD83D\uDCC2 Include Subdirectories", "\uD83D\uDD0C Active" }; public MonitoredDirectoryTableModel(List directories) { - this.directories = directories; + this.directories = MonitoredDirectoryUtil.filterMonitoredDirectoriesOnConfirmedPath(directories); } @Override @@ -58,8 +61,9 @@ public Object getValueAt(int rowIndex, int columnIndex) { * Get the MonitoredDirectory object at the specified row index. * * @param rowIndex the index of the row in the table. - * @return an Optional containing the MonitoredDirectory object at the specified row index, - * or an empty Optional if the index is out of bounds. + * @return an Optional containing the MonitoredDirectory object at the specified + * row index, + * or an empty Optional if the index is out of bounds. */ public Optional getDirectoryAt(int rowIndex) { if (rowIndex >= 0 && rowIndex < directories.size()) { @@ -68,4 +72,3 @@ public Optional getDirectoryAt(int rowIndex) { return Optional.empty(); } } - diff --git a/src/main/java/org/pwss/model/table/QuarantineTableModel.java b/src/main/java/org/pwss/model/table/QuarantineTableModel.java index 0262e4f..05cd750 100644 --- a/src/main/java/org/pwss/model/table/QuarantineTableModel.java +++ b/src/main/java/org/pwss/model/table/QuarantineTableModel.java @@ -4,7 +4,7 @@ import java.util.Optional; import javax.swing.table.AbstractTableModel; import org.pwss.model.entity.QuarantineMetadata; -import org.pwss.utils.OSUtils; +import org.pwss.util.OSUtil; /** * Table model for displaying quarantine metadata in a table. @@ -41,7 +41,7 @@ public Object getValueAt(int rowIndex, int columnIndex) { QuarantineMetadata metadata = data.get(rowIndex); return switch (columnIndex) { case 0 -> metadata.fileId(); - case 1 -> OSUtils.formatQuarantinePath(metadata.keyName()); + case 1 -> OSUtil.formatQuarantinePath(metadata.keyName()); case 2 -> "\uD83D\uDCE4"; default -> null; }; diff --git a/src/main/java/org/pwss/utils/AppTheme.java b/src/main/java/org/pwss/util/AppTheme.java similarity index 95% rename from src/main/java/org/pwss/utils/AppTheme.java rename to src/main/java/org/pwss/util/AppTheme.java index 0b86b3b..09b42ef 100644 --- a/src/main/java/org/pwss/utils/AppTheme.java +++ b/src/main/java/org/pwss/util/AppTheme.java @@ -1,4 +1,4 @@ -package org.pwss.utils; +package org.pwss.util; /** * Enum representing different application themes. diff --git a/src/main/java/org/pwss/utils/ConversionUtils.java b/src/main/java/org/pwss/util/ConversionUtil.java similarity index 98% rename from src/main/java/org/pwss/utils/ConversionUtils.java rename to src/main/java/org/pwss/util/ConversionUtil.java index 3c1e1a4..1399aab 100644 --- a/src/main/java/org/pwss/utils/ConversionUtils.java +++ b/src/main/java/org/pwss/util/ConversionUtil.java @@ -1,4 +1,4 @@ -package org.pwss.utils; +package org.pwss.util; /** * Utility class providing methods to convert between different units of data @@ -13,7 +13,7 @@ * comprehension or processing. *

*/ -public class ConversionUtils { +public class ConversionUtil { private static final long MEGABYTE = 1024L * 1024L; private static final long GIGABYTE = MEGABYTE * 1024L; diff --git a/src/main/java/org/pwss/utils/ErrorUtils.java b/src/main/java/org/pwss/util/ErrorUtil.java similarity index 92% rename from src/main/java/org/pwss/utils/ErrorUtils.java rename to src/main/java/org/pwss/util/ErrorUtil.java index beefac1..5338971 100644 --- a/src/main/java/org/pwss/utils/ErrorUtils.java +++ b/src/main/java/org/pwss/util/ErrorUtil.java @@ -1,9 +1,9 @@ -package org.pwss.utils; +package org.pwss.util; /** * Utility class for handling and formatting error messages. */ -public final class ErrorUtils { +public final class ErrorUtil { /** * Formats an error message by combining a constant text with a dynamic error diff --git a/src/main/java/org/pwss/utils/LiveFeedUtils.java b/src/main/java/org/pwss/util/LiveFeedUtil.java similarity index 97% rename from src/main/java/org/pwss/utils/LiveFeedUtils.java rename to src/main/java/org/pwss/util/LiveFeedUtil.java index 859a62d..15ee9fc 100644 --- a/src/main/java/org/pwss/utils/LiveFeedUtils.java +++ b/src/main/java/org/pwss/util/LiveFeedUtil.java @@ -1,9 +1,9 @@ -package org.pwss.utils; +package org.pwss.util; /** * Utility class for processing live feed entries. */ -public final class LiveFeedUtils { +public final class LiveFeedUtil { /** * A white check mark emoji used in live feed entries. diff --git a/src/main/java/org/pwss/utils/LoginUtils.java b/src/main/java/org/pwss/util/LoginUtil.java similarity index 97% rename from src/main/java/org/pwss/utils/LoginUtils.java rename to src/main/java/org/pwss/util/LoginUtil.java index a0a002f..4cd2d01 100644 --- a/src/main/java/org/pwss/utils/LoginUtils.java +++ b/src/main/java/org/pwss/util/LoginUtil.java @@ -1,4 +1,4 @@ -package org.pwss.utils; +package org.pwss.util; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -7,10 +7,10 @@ /** * Utility class for validating login inputs. */ -public final class LoginUtils { +public final class LoginUtil { // Private constructor to prevent instantiation - private LoginUtils() { + private LoginUtil() { // This constructor is intentionally empty. } diff --git a/src/main/java/org/pwss/utils/MonitoredDirectoryUtils.java b/src/main/java/org/pwss/util/MonitoredDirectoryUtil.java similarity index 78% rename from src/main/java/org/pwss/utils/MonitoredDirectoryUtils.java rename to src/main/java/org/pwss/util/MonitoredDirectoryUtil.java index 19e5dcf..73507b1 100644 --- a/src/main/java/org/pwss/utils/MonitoredDirectoryUtils.java +++ b/src/main/java/org/pwss/util/MonitoredDirectoryUtil.java @@ -1,7 +1,10 @@ -package org.pwss.utils; +package org.pwss.util; +import java.nio.file.Files; +import java.nio.file.Path; import java.time.Duration; import java.time.Instant; +import java.util.LinkedList; import java.util.List; import org.pwss.model.entity.MonitoredDirectory; import org.slf4j.Logger; @@ -9,17 +12,17 @@ /** * Utility class for handling operations related to monitored directories. */ -public final class MonitoredDirectoryUtils { +public final class MonitoredDirectoryUtil { /** * Logger instance for logging purposes */ - private final static Logger log = org.slf4j.LoggerFactory.getLogger(MonitoredDirectoryUtils.class); + private final static Logger log = org.slf4j.LoggerFactory.getLogger(MonitoredDirectoryUtil.class); /** * Private constructor to prevent instantiation */ - private MonitoredDirectoryUtils() { + private MonitoredDirectoryUtil() { } /** @@ -98,4 +101,25 @@ public static boolean isScanOlderThan1Minute(MonitoredDirectory dir) { return lastScan.isBefore(oneMinuteAgo); } + + /** + * Filters a list of monitored directories based on whether their paths exist in + * the filesystem. + * + * @param inputList The list of monitored directories to be filtered. + * @return A new list containing only the directories from the input list that + * have valid, confirmed paths. + */ + public static List filterMonitoredDirectoriesOnConfirmedPath( + List inputList) { + + List mDirectories = new LinkedList<>(); + + for (MonitoredDirectory m : inputList) { + if (Files.exists(Path.of(m.path()))) { + mDirectories.add(m); + } + } + return mDirectories; + } } diff --git a/src/main/java/org/pwss/utils/OSUtils.java b/src/main/java/org/pwss/util/OSUtil.java similarity index 98% rename from src/main/java/org/pwss/utils/OSUtils.java rename to src/main/java/org/pwss/util/OSUtil.java index e0e07d5..15da0ae 100644 --- a/src/main/java/org/pwss/utils/OSUtils.java +++ b/src/main/java/org/pwss/util/OSUtil.java @@ -1,13 +1,13 @@ -package org.pwss.utils; +package org.pwss.util; /** * Utility class for operating system detection and information. * Provides methods to determine the current OS type and related functionalities. */ -public final class OSUtils { +public final class OSUtil { // Private constructor to prevent instantiation - private OSUtils() { + private OSUtil() { throw new UnsupportedOperationException("Utility class cannot be instantiated"); } diff --git a/src/main/java/org/pwss/utils/ReportUtils.java b/src/main/java/org/pwss/util/ReportUtil.java similarity index 98% rename from src/main/java/org/pwss/utils/ReportUtils.java rename to src/main/java/org/pwss/util/ReportUtil.java index 687f88e..06a8dc2 100644 --- a/src/main/java/org/pwss/utils/ReportUtils.java +++ b/src/main/java/org/pwss/util/ReportUtil.java @@ -1,4 +1,4 @@ -package org.pwss.utils; +package org.pwss.util; import java.text.SimpleDateFormat; import java.util.Date; @@ -8,7 +8,7 @@ import org.pwss.model.entity.Scan; import org.pwss.model.entity.ScanSummary; -public class ReportUtils { +public class ReportUtil { /** * Formats a ScanSummary into a human-readable string. diff --git a/src/main/java/org/pwss/utils/StringConstants.java b/src/main/java/org/pwss/util/StringConstants.java similarity index 99% rename from src/main/java/org/pwss/utils/StringConstants.java rename to src/main/java/org/pwss/util/StringConstants.java index d4a74be..69fae7a 100644 --- a/src/main/java/org/pwss/utils/StringConstants.java +++ b/src/main/java/org/pwss/util/StringConstants.java @@ -1,4 +1,4 @@ -package org.pwss.utils; +package org.pwss.util; /** * A utility class that holds various string constants used throughout the application. diff --git a/src/main/java/org/pwss/utils/StringUtils.java b/src/main/java/org/pwss/util/StringUtil.java similarity index 97% rename from src/main/java/org/pwss/utils/StringUtils.java rename to src/main/java/org/pwss/util/StringUtil.java index beff46a..e7a10ac 100644 --- a/src/main/java/org/pwss/utils/StringUtils.java +++ b/src/main/java/org/pwss/util/StringUtil.java @@ -1,16 +1,16 @@ -package org.pwss.utils; +package org.pwss.util; /** * A utility class for string manipulation operations. * This class contains static methods to perform various string manipulations. */ -public final class StringUtils { +public final class StringUtil { /** * * Private constructor to prevent instantiation **/ - private StringUtils() { + private StringUtil() { } /** diff --git a/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java b/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java index b43e090..cecb7b6 100644 --- a/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java +++ b/src/main/java/org/pwss/view/popup_menu/MonitoredDirectoryPopupFactory.java @@ -18,8 +18,8 @@ import org.pwss.model.entity.MonitoredDirectory; import org.pwss.model.request.notes.RestoreNoteType; import org.pwss.model.table.MonitoredDirectoryTableModel; -import org.pwss.utils.StringConstants; -import org.pwss.utils.StringUtils; +import org.pwss.util.StringConstants; +import org.pwss.util.StringUtil; import org.pwss.view.popup_menu.listener.MonitoredDirectoryPopupListener; /** @@ -146,7 +146,7 @@ private JMenuItem getUpdateNoteItem(MonitoredDirectory dir) { updateNoteItem.addActionListener(e -> { // Label for directory path JLabel label = new JLabel( - StringConstants.MON_DIR_POPUP_UPDATE_NOTE_POPUP_PREFIX + StringUtils.prependSpace(dir.path())); + StringConstants.MON_DIR_POPUP_UPDATE_NOTE_POPUP_PREFIX + StringUtil.prependSpace(dir.path())); label.setAlignmentX(Component.LEFT_ALIGNMENT); // Text area for note input diff --git a/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListenerImpl.java b/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListenerImpl.java index 1080fab..52b07d9 100644 --- a/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListenerImpl.java +++ b/src/main/java/org/pwss/view/popup_menu/listener/MonitoredDirectoryPopupListenerImpl.java @@ -6,7 +6,7 @@ import org.pwss.model.request.notes.RestoreNoteType; import org.pwss.service.MonitoredDirectoryService; import org.pwss.service.NoteService; -import org.pwss.utils.StringConstants; +import org.pwss.util.StringConstants; import org.slf4j.LoggerFactory; /** diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.java b/src/main/java/org/pwss/view/screen/HomeScreen.java index e809c9e..78bbd28 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.java +++ b/src/main/java/org/pwss/view/screen/HomeScreen.java @@ -28,7 +28,7 @@ import javax.swing.plaf.FontUIResource; import javax.swing.text.StyleContext; import org.pwss.model.entity.MonitoredDirectory; -import org.pwss.utils.AppTheme; +import org.pwss.util.AppTheme; /** * The HomeScreen class represents the main screen of the application. diff --git a/src/test/java/org/pwss/utils/LoginUtilsTest.java b/src/test/java/org/pwss/util/LoginUtilTest.java similarity index 67% rename from src/test/java/org/pwss/utils/LoginUtilsTest.java rename to src/test/java/org/pwss/util/LoginUtilTest.java index b7ca8d6..de79da5 100644 --- a/src/test/java/org/pwss/utils/LoginUtilsTest.java +++ b/src/test/java/org/pwss/util/LoginUtilTest.java @@ -1,16 +1,15 @@ -package org.pwss.utils; +package org.pwss.util; import java.util.List; import org.junit.jupiter.api.Test; -import org.pwss.utils.LoginUtils.LoginValidationResult; - +import org.pwss.util.LoginUtil.LoginValidationResult; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -public class LoginUtilsTest { +public class LoginUtilTest { // --- Helper for readable assertions --- private void assertHasError(LoginValidationResult result, String expectedError) { @@ -21,7 +20,7 @@ private void assertHasError(LoginValidationResult result, String expectedError) @Test void testAllFieldsEmpty() { - LoginValidationResult result = LoginUtils.validateInput("", "", "", "", false); + LoginValidationResult result = LoginUtil.validateInput("", "", "", "", false); assertFalse(result.isValid()); assertEquals(3, result.errors().size()); assertHasError(result, "Username cannot be empty."); @@ -32,7 +31,7 @@ void testAllFieldsEmpty() { @Test void testNullInputsHandledGracefully() { assertDoesNotThrow(() -> { - LoginValidationResult result = LoginUtils.validateInput(null, null, null, null, false); + LoginValidationResult result = LoginUtil.validateInput(null, null, null, null, false); assertFalse(result.isValid()); assertTrue(result.errors().contains("Username cannot be empty.")); }); @@ -42,7 +41,7 @@ void testNullInputsHandledGracefully() { @Test void testMissingLicenseKey() { - LoginValidationResult result = LoginUtils.validateInput("user", "password", "", "", false); + LoginValidationResult result = LoginUtil.validateInput("user", "password", "", "", false); assertHasError(result, "License key cannot be empty."); } @@ -50,37 +49,37 @@ void testMissingLicenseKey() { @Test void testPasswordTooShort() { - LoginValidationResult result = LoginUtils.validateInput("user", "Ab1!", "Ab1!", "key123", true); + LoginValidationResult result = LoginUtil.validateInput("user", "Ab1!", "Ab1!", "key123", true); assertHasError(result, "Password must be at least 8 characters long."); } @Test void testConfirmPasswordMissing() { - LoginValidationResult result = LoginUtils.validateInput("user", "Abcd123!", "", "key123", true); + LoginValidationResult result = LoginUtil.validateInput("user", "Abcd123!", "", "key123", true); assertHasError(result, "Confirm Password cannot be empty."); } @Test void testPasswordsDoNotMatch() { - LoginValidationResult result = LoginUtils.validateInput("user", "Abcd123!", "Abcd1234!", "key123", true); + LoginValidationResult result = LoginUtil.validateInput("user", "Abcd123!", "Abcd1234!", "key123", true); assertHasError(result, "Password and Confirm Password do not match."); } @Test void testPasswordMissingUppercase() { - LoginValidationResult result = LoginUtils.validateInput("user", "abcd123!", "abcd123!", "key123", true); + LoginValidationResult result = LoginUtil.validateInput("user", "abcd123!", "abcd123!", "key123", true); assertHasError(result, "Password must contain at least one uppercase letter."); } @Test void testPasswordMissingDigit() { - LoginValidationResult result = LoginUtils.validateInput("user", "Abcdefg!", "Abcdefg!", "key123", true); + LoginValidationResult result = LoginUtil.validateInput("user", "Abcdefg!", "Abcdefg!", "key123", true); assertHasError(result, "Password must contain at least one digit."); } @Test void testPasswordMissingSpecialCharacter() { - LoginValidationResult result = LoginUtils.validateInput("user", "Abcdefg1", "Abcdefg1", "key123", true); + LoginValidationResult result = LoginUtil.validateInput("user", "Abcdefg1", "Abcdefg1", "key123", true); assertHasError(result, "Password must contain at least one special character."); } @@ -88,14 +87,14 @@ void testPasswordMissingSpecialCharacter() { @Test void testValidInputInCreateUserMode() { - LoginValidationResult result = LoginUtils.validateInput("user", "Abcd123!", "Abcd123!", "key123", true); + LoginValidationResult result = LoginUtil.validateInput("user", "Abcd123!", "Abcd123!", "key123", true); assertTrue(result.isValid(), "Expected validation to pass"); assertTrue(result.errors().isEmpty(), "Expected no errors"); } @Test void testValidInputInLoginMode() { - LoginValidationResult result = LoginUtils.validateInput("user", "password", "", "key123", false); + LoginValidationResult result = LoginUtil.validateInput("user", "password", "", "key123", false); assertTrue(result.isValid(), "Expected validation to pass in login mode"); } @@ -104,7 +103,7 @@ void testValidInputInLoginMode() { @Test void testFormatErrors() { List errors = List.of("Error 1", "Error 2", "Error 3"); - String formatted = LoginUtils.formatErrors(errors); + String formatted = LoginUtil.formatErrors(errors); assertEquals("Error 1\nError 2\nError 3", formatted); } } diff --git a/src/test/java/org/pwss/utils/OSUtilsTest.java b/src/test/java/org/pwss/util/OSUtilTest.java similarity index 75% rename from src/test/java/org/pwss/utils/OSUtilsTest.java rename to src/test/java/org/pwss/util/OSUtilTest.java index 3507f25..7c279ad 100644 --- a/src/test/java/org/pwss/utils/OSUtilsTest.java +++ b/src/test/java/org/pwss/util/OSUtilTest.java @@ -1,4 +1,4 @@ -package org.pwss.utils; +package org.pwss.util; import org.junit.jupiter.api.Test; @@ -6,11 +6,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -public class OSUtilsTest { +public class OSUtilTest { @Test void testNullInput() { - assertNull(OSUtils.formatQuarantinePath(null), "Null input should return null"); + assertNull(OSUtil.formatQuarantinePath(null), "Null input should return null"); } @Test @@ -18,13 +18,13 @@ void testSimplePathConversion() { final String input = "usr.local.bin.myfile.txt"; final String expected; - if (OSUtils.isWindows()) { + if (OSUtil.isWindows()) { expected = "usr\\local\\bin\\myfile.txt"; } else { expected = "usr/local/bin/myfile.txt"; } - assertEquals(expected, OSUtils.formatQuarantinePath(input), + assertEquals(expected, OSUtil.formatQuarantinePath(input), "Path separators should be converted correctly for the current OS"); } @@ -33,13 +33,13 @@ void testDoubleDotPreservation() { final String input = "etc..config.file.txt"; final String expected; - if (OSUtils.isWindows()) { + if (OSUtil.isWindows()) { expected = "etc\\.config\\file.txt"; } else { expected = "etc/.config/file.txt"; } - assertEquals(expected, OSUtils.formatQuarantinePath(input), + assertEquals(expected, OSUtil.formatQuarantinePath(input), "Double dots should be preserved correctly as literal dots in the output"); } @@ -48,13 +48,13 @@ void testExtensionPreservation() { final String input = "var.log.myapp.log"; final String expected; - if (OSUtils.isWindows()) { + if (OSUtil.isWindows()) { expected = "var\\log\\myapp.log"; } else { expected = "var/log/myapp.log"; } - assertEquals(expected, OSUtils.formatQuarantinePath(input), + assertEquals(expected, OSUtil.formatQuarantinePath(input), "File extensions should be preserved with a single dot"); } @@ -63,7 +63,7 @@ void testComplexMixedPath() { final String input; final String expected; - if (OSUtils.isWindows()) { + if (OSUtil.isWindows()) { input = "C_drive__.Program.Files.Java.myapp.jar"; expected = "C:\\Program\\Files\\Java\\myapp.jar"; } else { @@ -71,7 +71,7 @@ void testComplexMixedPath() { expected = "etc/.config/.file.txt"; } - assertEquals(expected, OSUtils.formatQuarantinePath(input), + assertEquals(expected, OSUtil.formatQuarantinePath(input), "Complex paths should convert consistently across OS types"); } } From f3f4754fb4a0fdd6f0bb129eba22e81b9594cfdf Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 17 Nov 2025 16:14:46 +0100 Subject: [PATCH 39/44] feat(gui): improve UI/UX and enhance diff workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated file icons - Adjusted full-scan flow so “Scan with differences” now navigates to the **Recent Diff** tab after user confirmation - Improved live feed accountability and clarity - Added visible diff count to the warning/confirmation dialog after directory scans - Performed minor UI/UX refinements and general cleanup --- .../org/pwss/controller/HomeController.java | 16 +++++++- src/main/java/org/pwss/util/ScanUtil.java | 37 +++++++++++++++++++ .../java/org/pwss/util/StringConstants.java | 1 - src/main/java/org/pwss/util/StringUtil.java | 2 +- .../java/org/pwss/view/screen/HomeScreen.java | 2 +- 5 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/pwss/util/ScanUtil.java diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index 174c3df..756f767 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -1,6 +1,7 @@ package org.pwss.controller; import com.fasterxml.jackson.core.JsonProcessingException; + import java.awt.Color; import java.awt.Component; import java.awt.event.ItemEvent; @@ -66,6 +67,7 @@ import org.pwss.util.MonitoredDirectoryUtil; import org.pwss.util.OSUtil; import org.pwss.util.ReportUtil; +import org.pwss.util.ScanUtil; import org.pwss.util.StringConstants; import org.pwss.view.popup_menu.MonitoredDirectoryPopupFactory; import org.pwss.view.popup_menu.listener.MonitoredDirectoryPopupListenerImpl; @@ -723,7 +725,8 @@ private void onFinishScan(boolean completed, boolean singleDirectory) { int choice; // Prompt the user to view scan results based on whether differences were found if (totalDiffCount.get() > 0) { - choice = screen.showOptionDialog(JOptionPane.WARNING_MESSAGE, StringConstants.SCAN_COMPLETED_DIFFS, + choice = screen.showOptionDialog(JOptionPane.WARNING_MESSAGE, + ScanUtil.constructDiffMessageString(totalDiffCount.get()), new String[] { StringConstants.GENERIC_YES, StringConstants.GENERIC_NO }, StringConstants.GENERIC_YES); } else { @@ -772,7 +775,7 @@ private void onFinishScan(boolean completed, boolean singleDirectory) { if (totalDiffCount.get() > 0) { // If full scan, and we have diffs, navigate to the diffs tab to show all // differences. - screen.getTabbedPane().setSelectedIndex(2); + screen.getTabbedPane().setSelectedIndex(3); } else { // If no diffs, navigate to the recent scans tab to show the most recent scan. screen.getTabbedPane().setSelectedIndex(0); @@ -845,13 +848,22 @@ private void startPollingScanLiveFeed(boolean singleDirectory, List Date: Mon, 17 Nov 2025 18:49:01 +0100 Subject: [PATCH 40/44] Update ScanTableModel Status column icon --- src/main/java/org/pwss/model/table/ScanTableModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/pwss/model/table/ScanTableModel.java b/src/main/java/org/pwss/model/table/ScanTableModel.java index a9ae6b4..6603d17 100644 --- a/src/main/java/org/pwss/model/table/ScanTableModel.java +++ b/src/main/java/org/pwss/model/table/ScanTableModel.java @@ -8,7 +8,7 @@ public class ScanTableModel extends AbstractTableModel { private final List scans; private final String[] columnNames = { - "\uD83D\uDCC1 Directory", "\uD83D\uDD59 Scan Time", "\uD83D\uDEA6 Status" + "\uD83D\uDCC1 Directory", "\uD83D\uDD59 Scan Time", "⌛ Status" }; public ScanTableModel(List scans) { From 4b85b27341213ec7e9adec8044511e3ef6fe5af8 Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 17 Nov 2025 22:03:40 +0100 Subject: [PATCH 41/44] Added RingBuffer Data Structure - Introduced a new RingBuffer class as a circular data structure for efficient data handling. - Integrated the RingBuffer into the `startPollingScanLiveFeed` method in HomeController to reduce memory overhead and improve system performance. --- .../org/pwss/controller/HomeController.java | 64 +++++++++------ .../org/pwss/data_structure/RingBuffer.java | 78 +++++++++++++++++++ 2 files changed, 120 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/pwss/data_structure/RingBuffer.java diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index 756f767..a12481d 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -25,6 +25,7 @@ import javax.swing.Timer; import org.pwss.app_settings.AppConfig; import org.pwss.controller.util.NavigationContext; +import org.pwss.data_structure.RingBuffer; import org.pwss.exception.metadata.MetadataKeyNameRetrievalException; import org.pwss.exception.monitored_directory.MonitoredDirectoryGetAllException; import org.pwss.exception.scan.GetAllMostRecentScansException; @@ -827,51 +828,70 @@ private void startPollingScanLiveFeed(boolean singleDirectory, List liveFeedBuffer = new RingBuffer<>(100); + + // Create and start a polling timer scanStatusTimer = new Timer(1000, e -> { try { - // Retrieve live feed updates + // Get live feed from backend LiveFeedResponse liveFeed = scanService.getLiveFeed(); - String currentLiveFeedText = screen.getLiveFeedText().getText(); + // Format the new update from the backend String newEntry = LiveFeedUtil.formatLiveFeedEntry(liveFeed.livefeed()); - String updatedLiveFeedText = currentLiveFeedText + newEntry; - screen.getLiveFeedText().setText(updatedLiveFeedText); - // Update the total difference count based on new warnings + // Add the new update to the ring buffer + liveFeedBuffer.add(newEntry); + + // Build the text for the live feed based on the latest updates in the buffer + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < liveFeedBuffer.size(); i++) { + sb.append(liveFeedBuffer.get(i)); + } + + // Update UI with the latest text + screen.getLiveFeedText().setText(sb.toString()); + + // Update the diff counter totalDiffCount.addAndGet(LiveFeedUtil.countWarnings(liveFeed.livefeed())); screen.getLiveFeedDiffCount().setText(StringConstants.SCAN_DIFFS_PREFIX + totalDiffCount); - // Update scanRunning state and refresh the UI if necessary + // Update scan status and UI as needed if (liveFeed.isScanRunning() != scanRunning) { scanRunning = liveFeed.isScanRunning(); refreshView(); } - if (!liveFeed.isScanRunning()) { - - log.debug("Stopped pulling the live feed due to completion."); - scanStatusTimer.stop(); // Terminate polling when the scan completes - log.debug("Retrieve the live feed one last time after receiving the final update from the server."); - liveFeed = scanService.getLiveFeed(); + // When the scan is complete, do a final pull + if (!liveFeed.isScanRunning()) { + log.debug("Scan completed, making final pull..."); - totalDiffCount.getAndAdd(LiveFeedUtil.countWarnings(liveFeed.livefeed())); - screen.getLiveFeedDiffCount().setText(StringConstants.SCAN_DIFFS_PREFIX + totalDiffCount); + // Do a final pull without time pressure + liveFeed = scanService.getLiveFeed(); // The last pull to get any final updates + // Add the last update to the buffer newEntry = LiveFeedUtil.formatLiveFeedEntry(liveFeed.livefeed()); if (!org.pwss.util.StringUtil.isEmpty(newEntry)) { - currentLiveFeedText = screen.getLiveFeedText().getText(); - updatedLiveFeedText = currentLiveFeedText + newEntry; - screen.getLiveFeedText().setText(updatedLiveFeedText); + liveFeedBuffer.add(newEntry); // Add the last pull to the buffer + + // Update UI with the last text + sb.setLength(0); // Rensa StringBuilder + for (int i = 0; i < liveFeedBuffer.size(); i++) { + sb.append(liveFeedBuffer.get(i)); // Add all updates to the buffer + } + screen.getLiveFeedText().setText(sb.toString()); } - onFinishScan(true, singleDirectory); + // Stop polling and end the process + scanStatusTimer.stop(); + onFinishScan(true, singleDirectory); // Complete the scan } } catch (LiveFeedException | ExecutionException | InterruptedException | JsonProcessingException ex) { - log.error(StringConstants.SCAN_LIVE_FEED_ERROR_PREFIX + " {}", ex.getMessage()); - log.debug("Debug Live Feed Exception", ex); - SwingUtilities.invokeLater(() -> screen.showError(StringConstants.SCAN_LIVE_FEED_ERROR_PREFIX)); + log.error("Error fetching live feed: {}", ex.getMessage()); + log.debug("Live Feed Exception", ex); + SwingUtilities.invokeLater(() -> screen.showError("Live feed error")); scanStatusTimer.stop(); // Stop polling on error - onFinishScan(false, singleDirectory); + onFinishScan(false, singleDirectory); // Handling errors } }); scanStatusTimer.start(); diff --git a/src/main/java/org/pwss/data_structure/RingBuffer.java b/src/main/java/org/pwss/data_structure/RingBuffer.java new file mode 100644 index 0000000..becde28 --- /dev/null +++ b/src/main/java/org/pwss/data_structure/RingBuffer.java @@ -0,0 +1,78 @@ +package org.pwss.data_structure; + +/** + * A thread-safe, fixed-size ring buffer implementation. + * + * @param The type of elements held in this collection. + */ +public final class RingBuffer { + private final Object[] buffer; + private int writeIndex = 0; + private int readIndex = 0; + private int size = 0; + + /** + * Constructs a new ring buffer with the specified capacity. + * + * @param capacity The maximum number of elements that can be stored in this + * buffer. + */ + public RingBuffer(int capacity) { + buffer = new Object[capacity]; + } + + /** + * Adds an item to the end of the buffer. If the buffer is full, this method + * will overwrite the oldest element with the new one. + * + * @param item The item to add to the buffer. + */ + public void add(T item) { + buffer[writeIndex] = item; + writeIndex = (writeIndex + 1) % buffer.length; + + if (size == buffer.length) { + // Buffer is full, move read pointer forward. + readIndex = (readIndex + 1) % buffer.length; + } else { + size++; + } + } + + /** + * Retrieves the item at the specified index from this buffer. The first element + * in + * the buffer has an index of zero. + * + * @param index The index of the item to retrieve. + * @return The item at the specified position in this buffer. + * @throws IndexOutOfBoundsException If the specified index is greater than or + * equal to the size of this + * buffer. + */ + public T get(int index) { + if (index >= size) { + throw new IndexOutOfBoundsException(); + } + int realIndex = (readIndex + index) % buffer.length; + return (T) buffer[realIndex]; + } + + /** + * Returns the number of elements in this buffer. + * + * @return The current number of elements in this buffer. + */ + public int size() { + return size; + } + + /** + * Checks whether this buffer is empty or not. + * + * @return True if this buffer contains no elements; false otherwise. + */ + public boolean isEmpty() { + return size == 0; + } +} \ No newline at end of file From 41d6dd6a2f9eebb49fed92db3282e40c6501135f Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 17 Nov 2025 22:10:33 +0100 Subject: [PATCH 42/44] Beginning of diff count label --- .../org/pwss/controller/HomeController.java | 4 + .../controller/ScanDetailsController.java | 4 + .../java/org/pwss/view/screen/HomeScreen.form | 12 +- .../java/org/pwss/view/screen/HomeScreen.java | 298 +++++------------- .../pwss/view/screen/ScanDetailsScreen.form | 12 +- .../pwss/view/screen/ScanDetailsScreen.java | 62 ++-- 6 files changed, 141 insertions(+), 251 deletions(-) diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index a12481d..3d1b4c8 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -534,6 +534,10 @@ public Component getListCellRendererComponent(JList list, Object value, int i screen.getMonitoredDirectoriesTable().setModel(monitoredDirectoryTableModel); screen.getMonitoredDirectoriesTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + // Update diffs count label + // TODO: Update with count from endpoint when available + screen.getDiffsCountLabel().setText("Diffs found: " + (recentDiffs != null ? recentDiffs.size() : 0)); + DiffTableModel diffTableModel = new DiffTableModel(recentDiffs != null ? recentDiffs : List.of()); screen.getDiffTable().setModel(diffTableModel); screen.getDiffTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); diff --git a/src/main/java/org/pwss/controller/ScanDetailsController.java b/src/main/java/org/pwss/controller/ScanDetailsController.java index 2710de6..593f663 100644 --- a/src/main/java/org/pwss/controller/ScanDetailsController.java +++ b/src/main/java/org/pwss/controller/ScanDetailsController.java @@ -141,6 +141,10 @@ void refreshView() { SimpleSummaryTableModel simpleSummaryTableModel = new SimpleSummaryTableModel(scanSummaries); screen.getScanSummaryTable().setModel(simpleSummaryTableModel); + // Update diffs count label + // TODO: Update with count from endpoint when available + screen.getDiffsCountLabel().setText("Diffs found: " + diffs.size()); + // Populate diffs table DiffTableModel diffTableModel = new DiffTableModel(diffs); screen.getDiffTable().setModel(diffTableModel); diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.form b/src/main/java/org/pwss/view/screen/HomeScreen.form index 9c6ae80..f77d6c5 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.form +++ b/src/main/java/org/pwss/view/screen/HomeScreen.form @@ -375,7 +375,7 @@
- + @@ -385,7 +385,7 @@ - + @@ -432,6 +432,14 @@ + + + + + + + + diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.java b/src/main/java/org/pwss/view/screen/HomeScreen.java index 0d238a9..25c5cb0 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.java +++ b/src/main/java/org/pwss/view/screen/HomeScreen.java @@ -228,6 +228,11 @@ public class HomeScreen extends BaseScreen { */ private JCheckBox maxHashExtractionFileSizeUnlimitedCheckbox; + /** + * Label to display the count of differences (diffs) detected. + */ + private JLabel diffsCountLabel; + @Override public String getScreenName() { return "Home"; @@ -504,16 +509,25 @@ public JSlider getMaxHashExtractionFileSizeSlider() { * unlimited. * * @return JCheckBox representing the "Unlimited" option for max hash extraction - * file size. + * file size. */ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { return maxHashExtractionFileSizeUnlimitedCheckbox; } + /** + * Returns the label that displays the count of differences (diffs) detected. + * + * @return The JLabel instance showing the diffs count + */ + public JLabel getDiffsCountLabel() { + return diffsCountLabel; + } + { - // GUI initializer generated by IntelliJ IDEA GUI Designer - // >>> IMPORTANT!! <<< - // DO NOT EDIT OR ADD ANY CODE HERE! +// GUI initializer generated by IntelliJ IDEA GUI Designer +// >>> IMPORTANT!! <<< +// DO NOT EDIT OR ADD ANY CODE HERE! $$$setupUI$$$(); } @@ -529,49 +543,29 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { rootPanel.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); tabbedPane = new JTabbedPane(); Font tabbedPaneFont = this.$$$getFont$$$(null, -1, 14, tabbedPane.getFont()); - if (tabbedPaneFont != null) - tabbedPane.setFont(tabbedPaneFont); + if (tabbedPaneFont != null) tabbedPane.setFont(tabbedPaneFont); tabbedPane.setTabPlacement(1); tabbedPane.setToolTipText(""); - rootPanel.add(tabbedPane, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_FIXED, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, false)); + rootPanel.add(tabbedPane, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); homeTab = new JPanel(); homeTab.setLayout(new GridLayoutManager(4, 1, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane.addTab("\uD83C\uDFE0 Home", homeTab); newDirectoryButton = new JButton(); newDirectoryButton.setText("New directory"); - homeTab.add(newDirectoryButton, - new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + homeTab.add(newDirectoryButton, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JSplitPane splitPane1 = new JSplitPane(); - homeTab.add(splitPane1, - new GridConstraints(1, 0, 2, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, - new Dimension(200, 200), null, 0, false)); + homeTab.add(splitPane1, new GridConstraints(1, 0, 2, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); final JPanel panel1 = new JPanel(); panel1.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); splitPane1.setLeftComponent(panel1); final JLabel label1 = new JLabel(); Font label1Font = this.$$$getFont$$$(null, Font.BOLD, 16, label1.getFont()); - if (label1Font != null) - label1.setFont(label1Font); + if (label1Font != null) label1.setFont(label1Font); label1.setText("Most recent scans"); - panel1.add(label1, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel1.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JScrollPane scrollPane1 = new JScrollPane(); scrollPane1.setToolTipText("Double click a scan to see details"); - panel1.add(scrollPane1, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, - 0, false)); + panel1.add(scrollPane1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); recentScanTable = new JTable(); recentScanTable.setAutoResizeMode(2); recentScanTable.setShowHorizontalLines(true); @@ -581,114 +575,68 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { splitPane1.setRightComponent(panel2); final JLabel label2 = new JLabel(); Font label2Font = this.$$$getFont$$$(null, Font.BOLD, 16, label2.getFont()); - if (label2Font != null) - label2.setFont(label2Font); + if (label2Font != null) label2.setFont(label2Font); label2.setText("Monitored directories"); - panel2.add(label2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel2.add(label2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JScrollPane scrollPane2 = new JScrollPane(); - panel2.add(scrollPane2, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, - 0, false)); - monitoredDirectoryList = new JList(); + panel2.add(scrollPane2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); + monitoredDirectoryList = new JList(); monitoredDirectoryList.setEnabled(true); monitoredDirectoryList.setSelectionMode(0); monitoredDirectoryList.setToolTipText("Monitored directories list"); scrollPane2.setViewportView(monitoredDirectoryList); notificationPanel = new JPanel(); notificationPanel.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 10, 0), -1, -1)); - homeTab.add(notificationPanel, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, true)); + homeTab.add(notificationPanel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); final JLabel label3 = new JLabel(); Font label3Font = this.$$$getFont$$$(null, Font.BOLD, 16, label3.getFont()); - if (label3Font != null) - label3.setFont(label3Font); + if (label3Font != null) label3.setFont(label3Font); label3.setText("Notifications"); - notificationPanel.add(label3, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); + notificationPanel.add(label3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); notificationTextArea = new JTextArea(); notificationTextArea.setEditable(false); - notificationPanel.add(notificationTextArea, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, - new Dimension(150, 50), null, 0, false)); + notificationPanel.add(notificationTextArea, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(150, 50), null, 0, false)); scanTab = new JPanel(); scanTab.setLayout(new GridLayoutManager(10, 4, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane.addTab("\uD83D\uDD0E Scan", scanTab); final JLabel label4 = new JLabel(); Font label4Font = this.$$$getFont$$$(null, Font.BOLD, 16, label4.getFont()); - if (label4Font != null) - label4.setFont(label4Font); + if (label4Font != null) label4.setFont(label4Font); label4.setHorizontalAlignment(0); label4.setText("Monitored directories"); - scanTab.add(label4, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanTab.add(label4, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JScrollPane scrollPane3 = new JScrollPane(); scrollPane3.setToolTipText("Right click a monitored directory for different actions"); - scanTab.add(scrollPane3, - new GridConstraints(1, 0, 5, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, - 0, false)); + scanTab.add(scrollPane3, new GridConstraints(1, 0, 5, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); monitoredDirectoriesTable = new JTable(); monitoredDirectoriesTable.setAutoResizeMode(2); scrollPane3.setViewportView(monitoredDirectoriesTable); liveFeedContainer = new JScrollPane(); - scanTab.add(liveFeedContainer, - new GridConstraints(1, 1, 8, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - new Dimension(450, -1), new Dimension(450, -1), null, 0, false)); + scanTab.add(liveFeedContainer, new GridConstraints(1, 1, 8, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, new Dimension(450, -1), new Dimension(450, -1), null, 0, false)); liveFeedText = new JTextPane(); liveFeedText.setEditable(false); liveFeedText.setText(""); liveFeedContainer.setViewportView(liveFeedText); liveFeedTitle = new JLabel(); Font liveFeedTitleFont = this.$$$getFont$$$(null, Font.BOLD, 16, liveFeedTitle.getFont()); - if (liveFeedTitleFont != null) - liveFeedTitle.setFont(liveFeedTitleFont); + if (liveFeedTitleFont != null) liveFeedTitle.setFont(liveFeedTitleFont); liveFeedTitle.setHorizontalAlignment(0); liveFeedTitle.setText("Scan logs"); - scanTab.add(liveFeedTitle, - new GridConstraints(0, 1, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanTab.add(liveFeedTitle, new GridConstraints(0, 1, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); liveFeedDiffCount = new JLabel(); liveFeedDiffCount.setText(""); - scanTab.add(liveFeedDiffCount, - new GridConstraints(9, 2, 1, 2, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); + scanTab.add(liveFeedDiffCount, new GridConstraints(9, 2, 1, 2, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); scanButton = new JButton(); scanButton.setText("Full scan"); - scanTab.add(scanButton, - new GridConstraints(6, 0, 4, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanTab.add(scanButton, new GridConstraints(6, 0, 4, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); clearFeedButton = new JButton(); clearFeedButton.setText("Clear feed"); - scanTab.add(clearFeedButton, - new GridConstraints(0, 3, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + scanTab.add(clearFeedButton, new GridConstraints(0, 3, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); filesTab = new JPanel(); filesTab.setLayout(new GridLayoutManager(2, 1, new Insets(10, 10, 10, 10), -1, -1)); - tabbedPane.addTab("📁 Files", filesTab); + tabbedPane.addTab("\uD83D\uDDC4\uFE0F Files", filesTab); final JSplitPane splitPane2 = new JSplitPane(); - filesTab.add(splitPane2, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, - new Dimension(200, 200), null, 0, false)); + filesTab.add(splitPane2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); final JScrollPane scrollPane4 = new JScrollPane(); splitPane2.setLeftComponent(scrollPane4); filesTable = new JTable(); @@ -698,11 +646,7 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { splitPane2.setRightComponent(panel3); final JSplitPane splitPane3 = new JSplitPane(); splitPane3.setOrientation(0); - panel3.add(splitPane3, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, - new Dimension(200, 200), null, 0, false)); + panel3.add(splitPane3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); final JScrollPane scrollPane5 = new JScrollPane(); splitPane3.setLeftComponent(scrollPane5); fileSummaryTable = new JTable(); @@ -714,47 +658,26 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { scrollPane6.setViewportView(scanSummaryDetails); final JPanel panel4 = new JPanel(); panel4.setLayout(new GridLayoutManager(5, 2, new Insets(0, 0, 0, 0), -1, -1)); - filesTab.add(panel4, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_NORTH, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, true)); + filesTab.add(panel4, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_NORTH, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); fileSearchField = new JTextField(); - panel4.add(fileSearchField, - new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, - new Dimension(150, -1), null, 0, false)); + panel4.add(fileSearchField, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); final JLabel label5 = new JLabel(); label5.setText("Search for file"); - panel4.add(label5, new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel4.add(label5, new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); searchContainingCheckBox = new JCheckBox(); searchContainingCheckBox.setText("File name contains"); - panel4.add(searchContainingCheckBox, - new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel4.add(searchContainingCheckBox, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); descendingCheckBox = new JCheckBox(); descendingCheckBox.setText("Descending"); - panel4.add(descendingCheckBox, - new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel4.add(descendingCheckBox, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); searchResultCount = new JLabel(); searchResultCount.setText(""); - panel4.add(searchResultCount, - new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); + panel4.add(searchResultCount, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); recentDiffsTab = new JPanel(); - recentDiffsTab.setLayout(new GridLayoutManager(2, 1, new Insets(10, 10, 10, 10), -1, -1)); + recentDiffsTab.setLayout(new GridLayoutManager(3, 1, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane.addTab("⚠\uFE0F Recent diffs", recentDiffsTab); final JSplitPane splitPane4 = new JSplitPane(); - recentDiffsTab.add(splitPane4, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, - new Dimension(200, 200), null, 0, false)); + recentDiffsTab.add(splitPane4, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); final JScrollPane scrollPane7 = new JScrollPane(); splitPane4.setLeftComponent(scrollPane7); diffTable = new JTable(); @@ -767,63 +690,41 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { scrollPane8.setViewportView(diffDetails); final JLabel label6 = new JLabel(); Font label6Font = this.$$$getFont$$$(null, Font.BOLD, 16, label6.getFont()); - if (label6Font != null) - label6.setFont(label6Font); + if (label6Font != null) label6.setFont(label6Font); label6.setText("Most recently detected diffs"); - recentDiffsTab.add(label6, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); + recentDiffsTab.add(label6, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + diffsCountLabel = new JLabel(); + diffsCountLabel.setText("Label"); + recentDiffsTab.add(diffsCountLabel, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); settingsTab = new JPanel(); settingsTab.setLayout(new GridLayoutManager(4, 1, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane.addTab("⚙\uFE0F Settings", settingsTab); final JSplitPane splitPane5 = new JSplitPane(); - settingsTab.add(splitPane5, - new GridConstraints(0, 0, 4, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, - new Dimension(200, 200), null, 0, false)); + settingsTab.add(splitPane5, new GridConstraints(0, 0, 4, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); final JPanel panel5 = new JPanel(); panel5.setLayout(new GridLayoutManager(7, 1, new Insets(0, 0, 0, 0), -1, -1)); splitPane5.setLeftComponent(panel5); final JLabel label7 = new JLabel(); Font label7Font = this.$$$getFont$$$(null, Font.BOLD, 14, label7.getFont()); - if (label7Font != null) - label7.setFont(label7Font); + if (label7Font != null) label7.setFont(label7Font); label7.setText("Settings"); - panel5.add(label7, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel5.add(label7, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final Spacer spacer1 = new Spacer(); - panel5.add(spacer1, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_CENTER, - GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); - themePicker = new JComboBox(); - panel5.add(themePicker, - new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); + panel5.add(spacer1, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); + themePicker = new JComboBox(); + panel5.add(themePicker, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); restartButton = new JButton(); restartButton.setText("Restart"); - panel5.add(restartButton, - new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel5.add(restartButton, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label8 = new JLabel(); label8.setText("© 2025 PWSS ORG"); - panel5.add(label8, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel5.add(label8, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JPanel panel6 = new JPanel(); panel6.setLayout(new GridLayoutManager(6, 1, new Insets(0, 0, 0, 0), -1, -1)); - panel5.add(panel6, - new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, false)); + panel5.add(panel6, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); showSplashScreenCheckBox = new JCheckBox(); showSplashScreenCheckBox.setText("Show splash screen"); - panel6.add(showSplashScreenCheckBox, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel6.add(showSplashScreenCheckBox, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); maxHashExtractionFileSizeSlider = new JSlider(); maxHashExtractionFileSizeSlider.setMajorTickSpacing(5120); maxHashExtractionFileSizeSlider.setMaximum(102400); @@ -833,80 +734,49 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { maxHashExtractionFileSizeSlider.setPaintTicks(true); maxHashExtractionFileSizeSlider.setPaintTrack(true); maxHashExtractionFileSizeSlider.setSnapToTicks(true); - panel6.add(maxHashExtractionFileSizeSlider, - new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); + panel6.add(maxHashExtractionFileSizeSlider, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label9 = new JLabel(); label9.setText("Max hash extraction file size (MB)"); - panel6.add(label9, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel6.add(label9, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JSeparator separator1 = new JSeparator(); - panel6.add(separator1, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, - false)); + panel6.add(separator1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); maxHashExtractionFileSizeValueLabel = new JLabel(); maxHashExtractionFileSizeValueLabel.setText("Selected:"); - panel6.add(maxHashExtractionFileSizeValueLabel, - new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); + panel6.add(maxHashExtractionFileSizeValueLabel, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); maxHashExtractionFileSizeUnlimitedCheckbox = new JCheckBox(); maxHashExtractionFileSizeUnlimitedCheckbox.setText("Unlimited"); - panel6.add(maxHashExtractionFileSizeUnlimitedCheckbox, - new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel6.add(maxHashExtractionFileSizeUnlimitedCheckbox, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label10 = new JLabel(); label10.setText("Theme"); - panel5.add(label10, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel5.add(label10, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JPanel panel7 = new JPanel(); panel7.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); splitPane5.setRightComponent(panel7); final JScrollPane scrollPane9 = new JScrollPane(); - panel7.add(scrollPane9, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, false)); + panel7.add(scrollPane9, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); quarantineTable = new JTable(); scrollPane9.setViewportView(quarantineTable); final JLabel label11 = new JLabel(); Font label11Font = this.$$$getFont$$$(null, Font.BOLD, 14, label11.getFont()); - if (label11Font != null) - label11.setFont(label11Font); + if (label11Font != null) label11.setFont(label11Font); label11.setText("Quarantined files"); - panel7.add(label11, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel7.add(label11, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); scanProgressContainer = new JPanel(); scanProgressContainer.setLayout(new GridLayoutManager(1, 2, new Insets(10, 10, 10, 10), -1, -1)); - rootPanel.add(scanProgressContainer, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_FIXED, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, true)); + rootPanel.add(scanProgressContainer, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); scanProgress = new JProgressBar(); scanProgress.setIndeterminate(true); - scanProgressContainer.add(scanProgress, - new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); + scanProgressContainer.add(scanProgress, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); scanProgressLabel = new JLabel(); scanProgressLabel.setText("Scan in progress"); - scanProgressContainer.add(scanProgressLabel, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); + scanProgressContainer.add(scanProgressLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); } /** * @noinspection ALL */ private Font $$$getFont$$$(String fontName, int style, int size, Font currentFont) { - if (currentFont == null) - return null; + if (currentFont == null) return null; String resultName; if (fontName == null) { resultName = currentFont.getName(); @@ -918,11 +788,9 @@ public JCheckBox getMaxHashExtractionFileSizeUnlimitedCheckbox() { resultName = currentFont.getName(); } } - Font font = new Font(resultName, style >= 0 ? style : currentFont.getStyle(), - size >= 0 ? size : currentFont.getSize()); + Font font = new Font(resultName, style >= 0 ? style : currentFont.getStyle(), size >= 0 ? size : currentFont.getSize()); boolean isMac = System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).startsWith("mac"); - Font fontWithFallback = isMac ? new Font(font.getFamily(), font.getStyle(), font.getSize()) - : new StyleContext().getFont(font.getFamily(), font.getStyle(), font.getSize()); + Font fontWithFallback = isMac ? new Font(font.getFamily(), font.getStyle(), font.getSize()) : new StyleContext().getFont(font.getFamily(), font.getStyle(), font.getSize()); return fontWithFallback instanceof FontUIResource ? fontWithFallback : new FontUIResource(fontWithFallback); } diff --git a/src/main/java/org/pwss/view/screen/ScanDetailsScreen.form b/src/main/java/org/pwss/view/screen/ScanDetailsScreen.form index 195d124..b904a3e 100644 --- a/src/main/java/org/pwss/view/screen/ScanDetailsScreen.form +++ b/src/main/java/org/pwss/view/screen/ScanDetailsScreen.form @@ -66,7 +66,7 @@ - + @@ -78,7 +78,7 @@ - + @@ -115,6 +115,14 @@ + + + + + + + + diff --git a/src/main/java/org/pwss/view/screen/ScanDetailsScreen.java b/src/main/java/org/pwss/view/screen/ScanDetailsScreen.java index 9c048b0..9ed37c6 100644 --- a/src/main/java/org/pwss/view/screen/ScanDetailsScreen.java +++ b/src/main/java/org/pwss/view/screen/ScanDetailsScreen.java @@ -6,6 +6,7 @@ import java.awt.Insets; import javax.swing.JButton; import javax.swing.JComponent; +import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; @@ -43,13 +44,10 @@ public class ScanDetailsScreen extends BaseScreen { * Text pane to show details of the differences (diffs) between scans. */ private JTextPane diffDetails; - - { - // GUI initializer generated by IntelliJ IDEA GUI Designer - // >>> IMPORTANT!! <<< - // DO NOT EDIT OR ADD ANY CODE HERE! - $$$setupUI$$$(); - } + /** + * Label to display the count of differences (diffs) detected. + */ + private JLabel diffsCountLabel; @Override public String getScreenName() { @@ -112,6 +110,22 @@ public JTextPane getScanSummaryDetails() { return scanSummaryDetails; } + /** + * Returns the label that displays the count of differences (diffs) detected. + * + * @return The JLabel instance showing the diffs count + */ + public JLabel getDiffsCountLabel() { + return diffsCountLabel; + } + + { +// GUI initializer generated by IntelliJ IDEA GUI Designer +// >>> IMPORTANT!! <<< +// DO NOT EDIT OR ADD ANY CODE HERE! + $$$setupUI$$$(); + } + /** * Method generated by IntelliJ IDEA GUI Designer * >>> IMPORTANT!! <<< @@ -123,20 +137,12 @@ public JTextPane getScanSummaryDetails() { rootPanel = new JPanel(); rootPanel.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); final JTabbedPane tabbedPane1 = new JTabbedPane(); - rootPanel.add(tabbedPane1, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, false)); + rootPanel.add(tabbedPane1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); final JPanel panel1 = new JPanel(); panel1.setLayout(new GridLayoutManager(1, 1, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane1.addTab("\uD83D\uDCCB Summaries", null, panel1, "Here you can see file summaries"); final JSplitPane splitPane1 = new JSplitPane(); - panel1.add(splitPane1, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, - new Dimension(200, 200), null, 0, false)); + panel1.add(splitPane1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); final JScrollPane scrollPane1 = new JScrollPane(); splitPane1.setLeftComponent(scrollPane1); scanSummaryTable = new JTable(); @@ -147,14 +153,10 @@ public JTextPane getScanSummaryDetails() { scanSummaryDetails.setEditable(false); scrollPane2.setViewportView(scanSummaryDetails); final JPanel panel2 = new JPanel(); - panel2.setLayout(new GridLayoutManager(1, 1, new Insets(10, 10, 10, 10), -1, -1)); + panel2.setLayout(new GridLayoutManager(2, 1, new Insets(10, 10, 10, 10), -1, -1)); tabbedPane1.addTab("⚠\uFE0F Diffs detected", null, panel2, "Here you can see the diffs detected for the scan"); final JSplitPane splitPane2 = new JSplitPane(); - panel2.add(splitPane2, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, - new Dimension(200, 200), null, 0, false)); + panel2.add(splitPane2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); final JScrollPane scrollPane3 = new JScrollPane(); splitPane2.setLeftComponent(scrollPane3); diffTable = new JTable(); @@ -164,19 +166,15 @@ public JTextPane getScanSummaryDetails() { diffDetails = new JTextPane(); diffDetails.setEditable(false); scrollPane4.setViewportView(diffDetails); + diffsCountLabel = new JLabel(); + diffsCountLabel.setText("Label"); + panel2.add(diffsCountLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JPanel panel3 = new JPanel(); panel3.setLayout(new GridLayoutManager(1, 1, new Insets(0, 10, 10, 10), -1, -1)); - rootPanel.add(panel3, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, true)); + rootPanel.add(panel3, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, true)); backButton = new JButton(); backButton.setText("Back"); - panel3.add(backButton, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel3.add(backButton, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); } /** From 0079bdf15f1d8b4aaccf92ec0aaca9fff29da181 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 17 Nov 2025 23:05:53 +0100 Subject: [PATCH 43/44] Added endpoint call for diff count --- .../org/pwss/controller/HomeController.java | 26 ++++++++++--- .../controller/ScanDetailsController.java | 13 +++++-- .../exception/scan/GetDiffCountException.java | 25 ++++++++++++ .../request/scan/ScanDiffsCountRequest.java | 9 +++++ .../java/org/pwss/service/ScanService.java | 39 +++++++++++++++++++ .../org/pwss/service/network/Endpoint.java | 4 ++ src/main/java/org/pwss/util/ScanUtil.java | 26 +++++++++++++ 7 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/pwss/exception/scan/GetDiffCountException.java create mode 100644 src/main/java/org/pwss/model/request/scan/ScanDiffsCountRequest.java diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index 3d1b4c8..70757ad 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -29,6 +29,7 @@ import org.pwss.exception.metadata.MetadataKeyNameRetrievalException; import org.pwss.exception.monitored_directory.MonitoredDirectoryGetAllException; import org.pwss.exception.scan.GetAllMostRecentScansException; +import org.pwss.exception.scan.GetDiffCountException; import org.pwss.exception.scan.GetMostRecentScansException; import org.pwss.exception.scan.GetScanDiffsException; import org.pwss.exception.scan.LiveFeedException; @@ -131,6 +132,11 @@ public final class HomeController extends BaseController { */ private List recentScans; + /** + * Count of differences found in recent scans. + */ + private int recentDiffsCount; + /** * List of most recent differences detected in the scans. */ @@ -263,8 +269,17 @@ void fetchDataAndRefreshView() { if (recentScans.isEmpty()) { recentDiffs = List.of(); } else { + List distinctRecentScans = ScanUtil.getScansDistinctByDirectory(recentScans); + + // Calculate diff count for the recent scans + recentDiffsCount = 0; + for (Scan scan : distinctRecentScans) { + int count = scanService.getScanDiffsCount(scan.id()); + recentDiffsCount += count; + } + // Fetch diffs for the most recent scan to show in the diffs table - recentDiffs = recentScans.stream() + recentDiffs = distinctRecentScans.stream() .flatMap(scan -> safeGetDiffs(scan.id()).stream()) .toList(); } @@ -282,8 +297,8 @@ void fetchDataAndRefreshView() { scanRunning = true; startPollingScanLiveFeed(false, Collections.emptyList()); } - } catch (MonitoredDirectoryGetAllException | ExecutionException | InterruptedException | JsonProcessingException - | GetAllMostRecentScansException | ScanStatusException e) { + } catch (MonitoredDirectoryGetAllException | ExecutionException | InterruptedException | + JsonProcessingException | GetAllMostRecentScansException | ScanStatusException | GetDiffCountException e) { log.error("Error getting data: {}", e.getMessage()); SwingUtilities.invokeLater(() -> screen.showError("Error getting data")); } @@ -535,8 +550,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i screen.getMonitoredDirectoriesTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // Update diffs count label - // TODO: Update with count from endpoint when available - screen.getDiffsCountLabel().setText("Diffs found: " + (recentDiffs != null ? recentDiffs.size() : 0)); + screen.getDiffsCountLabel().setText("Diffs found: " + recentDiffsCount); DiffTableModel diffTableModel = new DiffTableModel(recentDiffs != null ? recentDiffs : List.of()); screen.getDiffTable().setModel(diffTableModel); @@ -879,7 +893,7 @@ private void startPollingScanLiveFeed(boolean singleDirectory, List imp /** * Logger for logging messages within this controller. */ - private final org.slf4j.Logger log = LoggerFactory.getLogger(LoginController.class); + private final org.slf4j.Logger log = LoggerFactory.getLogger(ScanDetailsController.class); /** * Service responsible for managing scan summaries. @@ -49,12 +49,18 @@ public class ScanDetailsController extends BaseController imp * List of scan summaries. This can be empty if no summaries are available. */ private List scanSummaries; + /** * List of differences (diffs) between scans. This can be empty if no diffs are * available. */ private List diffs; + /** + * Count of differences found. This can be null if the count is not available. + */ + private Integer diffCount = 0; + /** * Constructs a ScanDetailsController with the given screen and initializes * services and lists. @@ -97,6 +103,8 @@ private void fetchData() { if (scanId != null) { // Fetch scan summaries for the scan scanSummaries = scanSummaryService.getScanSummaryForScan(scanId); + // Fetch diff count for the scan + diffCount = scanService.getScanDiffsCount(scanId); // Fetch diffs for the scan diffs = scanService.getDiffs(scanId, 1000, null, true); } @@ -142,8 +150,7 @@ void refreshView() { screen.getScanSummaryTable().setModel(simpleSummaryTableModel); // Update diffs count label - // TODO: Update with count from endpoint when available - screen.getDiffsCountLabel().setText("Diffs found: " + diffs.size()); + screen.getDiffsCountLabel().setText("Diffs found: " + diffCount); // Populate diffs table DiffTableModel diffTableModel = new DiffTableModel(diffs); diff --git a/src/main/java/org/pwss/exception/scan/GetDiffCountException.java b/src/main/java/org/pwss/exception/scan/GetDiffCountException.java new file mode 100644 index 0000000..9030fd9 --- /dev/null +++ b/src/main/java/org/pwss/exception/scan/GetDiffCountException.java @@ -0,0 +1,25 @@ +package org.pwss.exception.scan; + +import java.io.Serial; +import org.pwss.exception.PWSSbaseException; + +/** + * Exception thrown when there is an error while getting the difference count + * during a scan operation. + */ +public class GetDiffCountException extends PWSSbaseException { + /** + * Serial version UID for serialization. + */ + @Serial + private static final long serialVersionUID = 1L; + + /** + * Constructs a new GetDiffCountException with the specified detail message. + * + * @param message the detail message. + */ + public GetDiffCountException(String message) { + super(message); + } +} diff --git a/src/main/java/org/pwss/model/request/scan/ScanDiffsCountRequest.java b/src/main/java/org/pwss/model/request/scan/ScanDiffsCountRequest.java new file mode 100644 index 0000000..cca6fef --- /dev/null +++ b/src/main/java/org/pwss/model/request/scan/ScanDiffsCountRequest.java @@ -0,0 +1,9 @@ +package org.pwss.model.request.scan; + +/** + * Represents a request to count differences in a scan. + * + * @param scanId the identifier for the scan to count differences for + */ +public record ScanDiffsCountRequest(long scanId) { +} diff --git a/src/main/java/org/pwss/service/ScanService.java b/src/main/java/org/pwss/service/ScanService.java index 33698db..6f4d6b8 100644 --- a/src/main/java/org/pwss/service/ScanService.java +++ b/src/main/java/org/pwss/service/ScanService.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; import org.pwss.exception.scan.GetAllMostRecentScansException; +import org.pwss.exception.scan.GetDiffCountException; import org.pwss.exception.scan.GetMostRecentScansException; import org.pwss.exception.scan.GetScanDiffsException; import org.pwss.exception.scan.LiveFeedException; @@ -18,6 +19,7 @@ import org.pwss.model.entity.Scan; import org.pwss.model.request.scan.GetMostRecentScansRequest; import org.pwss.model.request.scan.GetScanDiffsRequest; +import org.pwss.model.request.scan.ScanDiffsCountRequest; import org.pwss.model.request.scan.StartScanAllRequest; import org.pwss.model.request.scan.StartSingleScanRequest; import org.pwss.model.response.LiveFeedResponse; @@ -200,6 +202,19 @@ public List getMostRecentScansAll() throws GetAllMostRecentScansException, }; } + /** + * Retrieves the diffs for a specific scan by sending a request to the SCAN_DIFFS endpoint. + * + * @param scanId The ID of the scan to retrieve diffs for. + * @param limit The maximum number of diffs to retrieve. + * @param sortField The field by which to sort the diffs. + * @param ascending Whether to sort the diffs in ascending order. + * @return A list of Diff objects if the request is successful. + * @throws GetScanDiffsException If the attempt to retrieve the scan diffs fails due to various reasons such as invalid credentials or server error. + * @throws ExecutionException If an error occurs during the asynchronous execution of the request. + * @throws InterruptedException If the thread executing the request is interrupted. + * @throws JsonProcessingException If an error occurs while processing JSON data. + */ public List getDiffs(long scanId, long limit, String sortField, boolean ascending) throws GetScanDiffsException, ExecutionException, InterruptedException, JsonProcessingException { String body = objectMapper.writeValueAsString(new GetScanDiffsRequest(scanId, limit, sortField, ascending)); HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.SCAN_DIFFS, body); @@ -212,4 +227,28 @@ public List getDiffs(long scanId, long limit, String sortField, boolean as default -> Collections.emptyList(); }; } + + /** + * Retrieves the count of diffs for a specific scan by sending a request to the DIFF_COUNT endpoint. + * + * @param scanId The ID of the scan to retrieve the diff count for. + * @return The count of diffs for the specified scan if the request is successful. + * @throws GetDiffCountException If the attempt to retrieve the scan diffs count fails due to various reasons such as invalid credentials, scan not found, or server error. + * @throws ExecutionException If an error occurs during the asynchronous execution of the request. + * @throws InterruptedException If the thread executing the request is interrupted. + * @throws JsonProcessingException If an error occurs while processing JSON data. + */ + public Integer getScanDiffsCount(long scanId) throws JsonProcessingException, ExecutionException, InterruptedException, GetDiffCountException { + String body = objectMapper.writeValueAsString(new ScanDiffsCountRequest(scanId)); + HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.DIFF_COUNT, body); + + return switch (response.statusCode()) { + case 200 -> Integer.parseInt(response.body()); + case 400 -> throw new GetDiffCountException("Get scan diffs count failed: Bad request."); + case 401 -> throw new GetDiffCountException("Get scan diffs count failed: User not authorized to perform this action."); + case 404 -> throw new GetDiffCountException("Get scan diffs count failed: Scan with the given ID not found."); + case 500 -> throw new GetDiffCountException("Get scan diffs count failed: An error occurred on the server while attempting to get the diff count."); + default -> null; + }; + } } diff --git a/src/main/java/org/pwss/service/network/Endpoint.java b/src/main/java/org/pwss/service/network/Endpoint.java index a02c421..a03c96a 100644 --- a/src/main/java/org/pwss/service/network/Endpoint.java +++ b/src/main/java/org/pwss/service/network/Endpoint.java @@ -54,6 +54,10 @@ public enum Endpoint { * Endpoint for retrieving diffs for a given scan. */ SCAN_DIFFS(HTTP_Method.POST, A.BASE_URL + A.SCAN + "diff"), + /** + * Endpoint for retrieving the count of diffs for a given scan. + */ + DIFF_COUNT(HTTP_Method.POST, A.BASE_URL + A.SCAN + "diff/count"), // Directory Endpoints diff --git a/src/main/java/org/pwss/util/ScanUtil.java b/src/main/java/org/pwss/util/ScanUtil.java index b93cebb..4811055 100644 --- a/src/main/java/org/pwss/util/ScanUtil.java +++ b/src/main/java/org/pwss/util/ScanUtil.java @@ -1,5 +1,12 @@ package org.pwss.util; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import org.pwss.model.entity.Scan; + /** * Utility class for constructing scan-related messages. */ @@ -34,4 +41,23 @@ private ScanUtil() { public static String constructDiffMessageString(long diffNumber) { return SCAN_COMPLETED_DIFFS_PREFIX + diffNumber + SCAN_COMPLETED_DIFFS_SUFFIX; } + + /** + * Filters the provided list of scans to return a list containing only distinct + * scans based on their monitored directory IDs. + * + * @param scans The list of scans to filter. + * @return A list of scans with distinct monitored directory IDs. + */ + public static List getScansDistinctByDirectory(List scans) { + return scans.stream() + .filter(distinctByKey(scan -> scan.monitoredDirectory().id())) + .toList(); + } + + // Helper method to create a predicate for distinct filtering based on a key extractor + private static Predicate distinctByKey(Function keyExtractor) { + Set seen = ConcurrentHashMap.newKeySet(); + return t -> seen.add(keyExtractor.apply(t)); + } } From d5edc68d1b9e99f25757ee66c0b48657a3d03199 Mon Sep 17 00:00:00 2001 From: pwgit-create Date: Mon, 17 Nov 2025 23:40:45 +0100 Subject: [PATCH 44/44] Set limit to Integer.MAX_VALUE -1 --- src/main/java/org/pwss/controller/HomeController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/pwss/controller/HomeController.java b/src/main/java/org/pwss/controller/HomeController.java index 70757ad..61d761c 100644 --- a/src/main/java/org/pwss/controller/HomeController.java +++ b/src/main/java/org/pwss/controller/HomeController.java @@ -316,7 +316,7 @@ void fetchDataAndRefreshView() { */ private List safeGetDiffs(long scanId) { try { - return scanService.getDiffs(scanId, 1000, null, false); + return scanService.getDiffs(scanId, (Integer.MAX_VALUE -1), null, false); } catch (GetScanDiffsException | ExecutionException | InterruptedException | JsonProcessingException e) { return List.of(); }