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/pom.xml b/pom.xml index 432db6f..6a6cbf1 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 @@ -60,25 +60,25 @@ - + com.fasterxml.jackson.core jackson-databind - 2.20.0 + 2.20.1 ch.qos.logback logback-classic - 1.5.20 + 1.5.21 org.junit.jupiter junit-jupiter-api - 6.0.0 + 6.0.1 test @@ -90,11 +90,11 @@ - + com.formdev flatlaf - 3.6.1 + 3.6.2 @@ -103,10 +103,10 @@ - + org.codehaus.mojo exec-maven-plugin - 3.5.1 + 3.6.2 ${project.mainClass} @@ -114,10 +114,10 @@ - + org.apache.maven.plugins maven-compiler-plugin - 3.14.0 + 3.14.1 ${maven.compiler.source} ${maven.compiler.target} @@ -126,7 +126,7 @@ - + org.apache.maven.plugins maven-assembly-plugin 3.7.1 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/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..859fb9d 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 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 6482af7..61d761c 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; @@ -13,7 +14,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; @@ -25,9 +25,11 @@ 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; +import org.pwss.exception.scan.GetDiffCountException; import org.pwss.exception.scan.GetMostRecentScansException; import org.pwss.exception.scan.GetScanDiffsException; import org.pwss.exception.scan.LiveFeedException; @@ -60,21 +62,24 @@ import org.pwss.service.NoteService; import org.pwss.service.ScanService; import org.pwss.service.ScanSummaryService; -import org.pwss.utils.AppTheme; -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.ScanUtil; +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; 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 public final class HomeController extends BaseController { /** @@ -127,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. */ @@ -167,6 +177,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,10 +198,12 @@ 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 public void onCreate() { + super.onCreate(); // Update theme picker screen.getThemePicker().removeAllItems(); // Populate the combo box with AppTheme values @@ -228,6 +245,7 @@ public Component getListCellRendererComponent(JList list, Object value, @Override public void onShow() { + super.onShow(); fetchDataAndRefreshView(); } @@ -240,17 +258,28 @@ 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 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()) { 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(); } @@ -268,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")); } @@ -287,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(); } @@ -295,6 +324,7 @@ private List safeGetDiffs(long scanId) { @Override public void reloadData() { + super.reloadData(); fetchDataAndRefreshView(); } @@ -359,7 +389,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(""); } @@ -383,7 +413,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(""); } @@ -411,6 +441,45 @@ 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 = ConversionUtil.megabytesToBytes(valueMegabytes); + + // Set App config value to size in bytes + if (AppConfig.setMaxHashExtractionFileSize(valueBytes)) { + screen.getMaxHashExtractionFileSizeValueLabel().setText(valueMegabytes + " MB"); + maxFileSizeForHashExtraction = ConversionUtil.megabytesToBytes(valueMegabytes); + } else { + log.error("Failed to update max hash extraction file size in app config."); + } + }); + screen.getMaxHashExtractionFileSizeUnlimitedCheckbox().addActionListener(l -> { + // If checked set the max size to -1L + 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.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.getMaxHashExtractionFileSizeSlider().getValue(); + log.debug("Setting max hash extraction file size to {} MB", sliderValueMegabytes); + long sliderValueBytes = ConversionUtil.megabytesToBytes(sliderValueMegabytes); + if (AppConfig.setMaxHashExtractionFileSize(sliderValueBytes)) { + maxFileSizeForHashExtraction = sliderValueBytes; + screen.getMaxHashExtractionFileSizeValueLabel().setText(sliderValueMegabytes + " MB"); + screen.getMaxHashExtractionFileSizeSlider().setEnabled(true); + } else { + log.error("Failed to update max hash extraction file size in app config."); + } + } + }); } @Override @@ -418,7 +487,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(); @@ -464,7 +533,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 { @@ -480,6 +549,9 @@ public Component getListCellRendererComponent(JList list, Object value, int i screen.getMonitoredDirectoriesTable().setModel(monitoredDirectoryTableModel); screen.getMonitoredDirectoriesTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + // Update diffs count label + screen.getDiffsCountLabel().setText("Diffs found: " + recentDiffsCount); + DiffTableModel diffTableModel = new DiffTableModel(recentDiffs != null ? recentDiffs : List.of()); screen.getDiffTable().setModel(diffTableModel); screen.getDiffTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -492,7 +564,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) { @@ -550,6 +622,20 @@ public Component getListCellRendererComponent(JList list, Object value, int i } }); })); + + if (maxFileSizeForHashExtraction != -1L) { + screen.getMaxHashExtractionFileSizeUnlimitedCheckbox().setSelected(false); + screen.getMaxHashExtractionFileSizeSlider().setEnabled(true); + + final int maxSliderValueMegabytes = Math + .toIntExact(ConversionUtil.bytesToMegabytes(maxFileSizeForHashExtraction)); + screen.getMaxHashExtractionFileSizeSlider().setValue(maxSliderValueMegabytes); + screen.getMaxHashExtractionFileSizeValueLabel().setText(maxSliderValueMegabytes + " MB"); + } else { + screen.getMaxHashExtractionFileSizeUnlimitedCheckbox().setSelected(true); + screen.getMaxHashExtractionFileSizeSlider().setEnabled(false); + screen.getMaxHashExtractionFileSizeValueLabel().setText("Unlimited"); + } } /** @@ -595,7 +681,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 { @@ -605,7 +691,7 @@ public void performStartScan(boolean singleDirectory) { } } else { baseLineScan = false; - startScanSuccess = scanService.startScan(); + startScanSuccess = scanService.startScan(maxFileSizeForHashExtraction); scanningDirs.addAll(allMonitoredDirectories); } SwingUtilities.invokeLater(() -> { @@ -614,7 +700,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 { @@ -625,7 +711,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(ErrorUtil.formatErrorMessage(StringConstants.SCAN_START_ERROR, e.getMessage()))); } } @@ -657,7 +744,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 { @@ -706,7 +794,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); @@ -758,42 +846,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(); - String newEntry = LiveFeedUtils.formatLiveFeedEntry(liveFeed.livefeed()); - String updatedLiveFeedText = currentLiveFeedText + newEntry; - screen.getLiveFeedText().setText(updatedLiveFeedText); + // Format the new update from the backend + String newEntry = LiveFeedUtil.formatLiveFeedEntry(liveFeed.livefeed()); + + // 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 the total difference count based on new warnings - totalDiffCount.addAndGet(LiveFeedUtils.countWarnings(liveFeed.livefeed())); + // 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(); } + + // When the scan is complete, do a final pull if (!liveFeed.isScanRunning()) { + log.debug("Scan completed, making final pull..."); - scanStatusTimer.stop(); // Terminate polling when the scan completes + // Do a final pull without time pressure + liveFeed = scanService.getLiveFeed(); // The last pull to get any final updates - liveFeed = scanService.getLiveFeed(); + // Add the last update to the buffer + newEntry = LiveFeedUtil.formatLiveFeedEntry(liveFeed.livefeed()); + if (!org.pwss.util.StringUtil.isEmpty(newEntry)) { + liveFeedBuffer.add(newEntry); // Add the last pull to the buffer - totalDiffCount.getAndAdd(LiveFeedUtils.countWarnings(liveFeed.livefeed())); - screen.getLiveFeedDiffCount().setText(StringConstants.SCAN_DIFFS_PREFIX + totalDiffCount); + // Update UI with the last text + sb.setLength(0); // Clear 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/controller/LoginController.java b/src/main/java/org/pwss/controller/LoginController.java index 7e3a4a4..6a10f0f 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; @@ -10,10 +11,11 @@ import org.pwss.navigation.NavigationEvents; import org.pwss.navigation.Screen; import org.pwss.service.AuthService; +import org.pwss.util.LoginUtil; +import org.pwss.util.StringConstants; import org.pwss.view.screen.LoginScreen; import org.slf4j.LoggerFactory; - import static org.pwss.app_settings.AppConfig.LICENSE_KEY; /** @@ -58,41 +60,50 @@ public LoginController(LoginScreen view) { @Override public void onShow() { - getScreen().getUsernameField().setText(""); - getScreen().getPasswordField().setText(""); + 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 + final int frameHeight = createUserMode ? 225 : 200; + final int offset = licenseKeySet ? 0 : 50; + screen.getParentFrame().setSize(450, frameHeight + offset); refreshView(); } @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); } /** @@ -136,7 +147,22 @@ 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! + + 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) { + createUserAndLogin(); + } else { + log.debug("User creation cancelled by user."); + } } else { // Handle normal login logic here performLogin(); @@ -144,31 +170,23 @@ 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(); + String username = screen.getUsername(); + String password = screen.getPassword(); + String confirmPassword = screen.getConfirmPassword(); + String licenseKey = licenseKeySet ? LICENSE_KEY : screen.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; + LoginUtil.LoginValidationResult result = LoginUtil.validateInput(username, password, confirmPassword, + licenseKey, createUserMode); + if (!result.isValid()) { + screen.showError(LoginUtil.formatErrors(result.errors())); } - return true; + return result.isValid(); } /** @@ -186,9 +204,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); @@ -224,9 +242,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); @@ -235,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/NewDirectoryController.java b/src/main/java/org/pwss/controller/NewDirectoryController.java index ad5da69..84cb407 100644 --- a/src/main/java/org/pwss/controller/NewDirectoryController.java +++ b/src/main/java/org/pwss/controller/NewDirectoryController.java @@ -5,7 +5,8 @@ import org.pwss.navigation.NavigationEvents; import org.pwss.navigation.Screen; import org.pwss.service.MonitoredDirectoryService; -import org.pwss.utils.StringConstants; +import org.pwss.util.OSUtil; +import org.pwss.util.StringConstants; import org.pwss.view.screen.NewDirectoryScreen; /** @@ -35,6 +36,7 @@ public NewDirectoryController(NewDirectoryScreen screen) { @Override public void onShow() { + super.onShow(); // Reset selected path when the screen is shown selectedPath = null; refreshView(); @@ -42,16 +44,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 (OSUtil.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 +83,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); } diff --git a/src/main/java/org/pwss/controller/ScanDetailsController.java b/src/main/java/org/pwss/controller/ScanDetailsController.java index 1222bfd..0c810b7 100644 --- a/src/main/java/org/pwss/controller/ScanDetailsController.java +++ b/src/main/java/org/pwss/controller/ScanDetailsController.java @@ -15,16 +15,22 @@ import org.pwss.service.FileService; import org.pwss.service.ScanService; import org.pwss.service.ScanSummaryService; -import org.pwss.utils.OSUtils; -import org.pwss.utils.ReportUtils; -import org.pwss.utils.StringConstants; +import org.pwss.util.OSUtil; +import org.pwss.util.ReportUtil; +import org.pwss.util.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(ScanDetailsController.class); + /** * Service responsible for managing scan summaries. */ @@ -43,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. @@ -66,6 +78,7 @@ public ScanDetailsController(ScanDetailsScreen screen) { @Override public void onShow() { + super.onShow(); if (scanSummaries != null && !scanSummaries.isEmpty()) { // Clear existing data scanSummaries = List.of(); @@ -90,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); } @@ -107,7 +122,7 @@ void initListeners() { int selectedRow = screen.getScanSummaryTable().getSelectedRow(); if (selectedRow >= 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(""); @@ -118,7 +133,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(""); } @@ -134,12 +149,16 @@ void refreshView() { SimpleSummaryTableModel simpleSummaryTableModel = new SimpleSummaryTableModel(scanSummaries); screen.getScanSummaryTable().setModel(simpleSummaryTableModel); + // Update diffs count label + screen.getDiffsCountLabel().setText("Diffs found: " + diffCount); + // Populate diffs table DiffTableModel diffTableModel = new DiffTableModel(diffs); 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 @@ -150,22 +169,29 @@ public void onCellButtonClicked(int row, int column) { diff.ifPresent(d -> { int choice = screen.showOptionDialog( JOptionPane.WARNING_MESSAGE, - OSUtils.getQuarantineWarningMessage(), - new String[]{StringConstants.GENERIC_YES, StringConstants.GENERIC_NO}, - StringConstants.GENERIC_NO - ); + OSUtil.getQuarantineWarningMessage(), + 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/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 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/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/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/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/model/table/MonitoredDirectoryTableModel.java b/src/main/java/org/pwss/model/table/MonitoredDirectoryTableModel.java index cc05bd2..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\uDEA6 Note", "\uD83D\uDD59 Last Scanned", "\uD83D\uDEE1️ Baseline Established", "\uD83D\uDDC2️ Include Subdirectories" + "\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 @@ -35,7 +38,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 +52,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; }; } @@ -57,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()) { @@ -67,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/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) { 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 36092fc..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(400, 200), + LOGIN(450, 250), /** * The home screen displayed after successful login. 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..8e3b9bb 100644 --- a/src/main/java/org/pwss/service/MonitoredDirectoryService.java +++ b/src/main/java/org/pwss/service/MonitoredDirectoryService.java @@ -153,48 +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 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."); - case 422 -> - throw new UpdateMonitoredDirectoryException( - "Update monitored directory failed: Unprocessable entity - invalid input data."); + 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/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/ScanService.java b/src/main/java/org/pwss/service/ScanService.java index c932563..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,8 @@ 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; import org.pwss.service.network.Endpoint; @@ -39,23 +42,25 @@ 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 { - HttpResponse response = PwssHttpClient.getInstance().request(Endpoint.START_SCAN, null); + 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); 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; }; } @@ -64,14 +69,15 @@ 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 { - String body = objectMapper.writeValueAsString(new StartSingleScanRequest(id)); + 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); return switch (response.statusCode()) { @@ -196,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); @@ -204,8 +223,32 @@ 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(); }; } + + /** + * 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/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; diff --git a/src/main/java/org/pwss/service/network/Endpoint.java b/src/main/java/org/pwss/service/network/Endpoint.java index c6b6ef3..a03c96a 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. */ @@ -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/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/util/ConversionUtil.java b/src/main/java/org/pwss/util/ConversionUtil.java new file mode 100644 index 0000000..1399aab --- /dev/null +++ b/src/main/java/org/pwss/util/ConversionUtil.java @@ -0,0 +1,100 @@ +package org.pwss.util; + +/** + * 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 ConversionUtil { + 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/util/ErrorUtil.java b/src/main/java/org/pwss/util/ErrorUtil.java new file mode 100644 index 0000000..5338971 --- /dev/null +++ b/src/main/java/org/pwss/util/ErrorUtil.java @@ -0,0 +1,21 @@ +package org.pwss.util; + +/** + * Utility class for handling and formatting error messages. + */ +public final class ErrorUtil { + + /** + * 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/util/LiveFeedUtil.java b/src/main/java/org/pwss/util/LiveFeedUtil.java new file mode 100644 index 0000000..15ee9fc --- /dev/null +++ b/src/main/java/org/pwss/util/LiveFeedUtil.java @@ -0,0 +1,70 @@ +package org.pwss.util; + +/** + * Utility class for processing live feed entries. + */ +public final class LiveFeedUtil { + + /** + * 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. + * + * @param rawEntry the raw live feed text from the service + * @return formatted live feed text + */ + public static String formatLiveFeedEntry(String rawEntry) { + if (rawEntry == null || rawEntry.isEmpty()) { + return ""; + } + + return rawEntry + .replace(WHITE_CHECK_MARK, WHITE_CHECK_MARK_REPLACE) + .replace(WARNING, WARNING_REPLACE) + .replace(FILE_TO_BIG_MESSAGE, FILE_TO_BIG_MESSAGE_REPLACE) + .trim(); + } + + /** + * Counts the number of warnings in a live feed entry. + * Warnings are indicated by the "⚠" symbol. + * + * @param entry the live feed entry text + * @return the count of warnings in the entry + */ + public static int countWarnings(String entry) { + if (entry == null || entry.isEmpty()) { + return 0; + } + + return (int) entry.codePoints() + .filter(c -> c == 0x26A0) // ⚠ + .count(); + } +} diff --git a/src/main/java/org/pwss/util/LoginUtil.java b/src/main/java/org/pwss/util/LoginUtil.java new file mode 100644 index 0000000..4cd2d01 --- /dev/null +++ b/src/main/java/org/pwss/util/LoginUtil.java @@ -0,0 +1,113 @@ +package org.pwss.util; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for validating login inputs. + */ +public final class LoginUtil { + + // Private constructor to prevent instantiation + private LoginUtil() { + // This constructor is intentionally empty. + } + + /** + * Validates the login input fields. + * + * @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 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); + } + + /** + * Formats a list of error messages into a single string. + * + * @param errors List of error messages. + * @return Formatted error string. + */ + 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"); + } + + 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 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/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 97% rename from src/main/java/org/pwss/utils/OSUtils.java rename to src/main/java/org/pwss/util/OSUtil.java index b86fa59..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"); } @@ -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."; 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/util/ScanUtil.java b/src/main/java/org/pwss/util/ScanUtil.java new file mode 100644 index 0000000..4811055 --- /dev/null +++ b/src/main/java/org/pwss/util/ScanUtil.java @@ -0,0 +1,63 @@ +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. + */ +public final class ScanUtil { + + /** + * Prefix string used in scan completion messages indicating the number of + * differences found. + */ + private static final String SCAN_COMPLETED_DIFFS_PREFIX = "Scan completed with "; + + /** + * Suffix string used in scan completion messages providing additional + * information about viewing details. + */ + private static final String SCAN_COMPLETED_DIFFS_SUFFIX = " differences found.\nDo you wish to see the details?\nYou can always view results later in the recent scans table."; + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private ScanUtil() { + // Prevent instantiation + } + + /** + * Constructs a scan completion message with the specified number of differences + * found. + * + * @param diffNumber The number of differences found during the scan. + * @return A string containing the complete scan message. + */ + 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)); + } +} diff --git a/src/main/java/org/pwss/utils/StringConstants.java b/src/main/java/org/pwss/util/StringConstants.java similarity index 82% rename from src/main/java/org/pwss/utils/StringConstants.java rename to src/main/java/org/pwss/util/StringConstants.java index 29526f1..eed2027 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. @@ -27,10 +27,9 @@ 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."; public static final String SCAN_NOT_COMPLETED = "Scan not completed."; public static final String SCAN_ESTABLISHING_BASELINE = "Establishing baseline for directory: "; @@ -48,7 +47,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"; @@ -56,7 +62,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 -"; 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/util/StringUtil.java b/src/main/java/org/pwss/util/StringUtil.java new file mode 100644 index 0000000..49c0f62 --- /dev/null +++ b/src/main/java/org/pwss/util/StringUtil.java @@ -0,0 +1,120 @@ +package org.pwss.util; + +/** + * A utility class for string manipulation operations. + * This class contains static methods to perform various string manipulations. + */ +public final class StringUtil { + + /** + * + * Private constructor to prevent instantiation + **/ + private StringUtil() { + } + + /** + * 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); + } + + /** + * 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 + */ + public static boolean isEmpty(String str) { + return str == null || str.isEmpty(); + } +} \ No newline at end of file diff --git a/src/main/java/org/pwss/utils/LiveFeedUtils.java b/src/main/java/org/pwss/utils/LiveFeedUtils.java deleted file mode 100644 index c687f7b..0000000 --- a/src/main/java/org/pwss/utils/LiveFeedUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.pwss.utils; - -public final class LiveFeedUtils { - - /** - * Formats a raw live feed entry for improved readability. - * Currently, inserts line breaks after certain emojis. - * - * @param rawEntry the raw live feed text from the service - * @return formatted live feed text - */ - public static String formatLiveFeedEntry(String rawEntry) { - if (rawEntry == null || rawEntry.isEmpty()) { - return ""; - } - - return rawEntry - .replace("✅", "✅\n") - .replace("⚠️", "⚠️\n") - .trim(); - } - - /** - * Counts the number of warnings in a live feed entry. - * Warnings are indicated by the "⚠" symbol. - * - * @param entry the live feed entry text - * @return the count of warnings in the entry - */ - public static int countWarnings(String entry) { - if (entry == null || entry.isEmpty()) { - return 0; - } - - return (int) entry.codePoints() - .filter(c -> c == 0x26A0) // ⚠ - .count(); - } -} 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..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,12 +18,15 @@ 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.util.StringConstants; +import org.pwss.util.StringUtil; 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,18 +42,23 @@ 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 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); @@ -62,8 +70,11 @@ public JPopupMenu create(JTable table, int viewRow) { JMenuItem restoreNoteItem = getRestoreNoteItem(dir); // Assemble menu - menu.add(scanItem); - menu.add(editDirectory); + if (dir.isActive()) { + menu.add(scanItem); + } + menu.add(toggleActiveMenuItem); + menu.add(toggleIncludeSubDirsItem); menu.addSeparator(); menu.add(showNoteItem); menu.add(updateNoteItem); @@ -75,14 +86,30 @@ 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 editing the directory + * @return the JMenuItem for toggling the active status */ - private JMenuItem getEditDirectoryItem(MonitoredDirectory dir) { - JMenuItem editItem = new JMenuItem(StringConstants.MON_DIR_POPUP_EDIT_DIR); - editItem.addActionListener(e -> listener.onEditDirectory(dir)); + 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 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); + editItem.addActionListener(e -> listener.onToggleIncludeSubdirectories(dir)); return editItem; } @@ -103,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; } @@ -119,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 + StringUtil.prependSpace(dir.path())); label.setAlignmentX(Component.LEFT_ALIGNMENT); // Text area for note input @@ -146,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) @@ -182,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; } @@ -223,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) { @@ -255,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 @@ -272,4 +295,3 @@ private JMenuItem getResetBaselineItem(MonitoredDirectory dir) { return resetBaselineItem; } } - 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..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; /** @@ -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 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.form b/src/main/java/org/pwss/view/screen/HomeScreen.form index 07a4cc5..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 @@ + + + + + + + + @@ -495,7 +503,7 @@ - + @@ -511,6 +519,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/pwss/view/screen/HomeScreen.java b/src/main/java/org/pwss/view/screen/HomeScreen.java index 7e137b3..25c5cb0 100644 --- a/src/main/java/org/pwss/view/screen/HomeScreen.java +++ b/src/main/java/org/pwss/view/screen/HomeScreen.java @@ -1,9 +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 java.awt.Dimension; import java.awt.Font; import java.awt.Insets; @@ -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; @@ -26,8 +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. @@ -42,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; @@ -211,8 +213,28 @@ public class HomeScreen extends BaseScreen { */ private JCheckBox showSplashScreenCheckBox; + /** + * Slider for setting the maximum file size for hash extraction. + */ + private JSlider maxHashExtractionFileSizeSlider; + + /** + * Label displaying the value of the maximum file size for hash extraction. + */ + private JLabel maxHashExtractionFileSizeValueLabel; + + /** + * Checkbox to set the maximum hash extraction file size to unlimited. + */ + private JCheckBox maxHashExtractionFileSizeUnlimitedCheckbox; + + /** + * Label to display the count of differences (diffs) detected. + */ + private JLabel diffsCountLabel; + @Override - protected String getScreenName() { + public String getScreenName() { return "Home"; } @@ -464,6 +486,44 @@ 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 getMaxHashExtractionFileSizeSlider() { + return maxHashExtractionFileSizeSlider; + } + + /** + * 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 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!! <<< @@ -614,10 +674,10 @@ public JTable getQuarantineTable() { 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)); 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(); @@ -633,6 +693,9 @@ public JTable getQuarantineTable() { 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)); + 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); @@ -657,14 +720,35 @@ 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(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)); + 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(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("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 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)); + 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)); + 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)); final JPanel panel7 = new JPanel(); panel7.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); splitPane5.setRightComponent(panel7); @@ -672,11 +756,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)); diff --git a/src/main/java/org/pwss/view/screen/LoginScreen.form b/src/main/java/org/pwss/view/screen/LoginScreen.form index f3ace95..16535c0 100644 --- a/src/main/java/org/pwss/view/screen/LoginScreen.form +++ b/src/main/java/org/pwss/view/screen/LoginScreen.form @@ -1,35 +1,100 @@
- + - + - - - - - - - - - - + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -52,46 +117,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/org/pwss/view/screen/LoginScreen.java b/src/main/java/org/pwss/view/screen/LoginScreen.java index 3cbeef4..f2e80e7 100644 --- a/src/main/java/org/pwss/view/screen/LoginScreen.java +++ b/src/main/java/org/pwss/view/screen/LoginScreen.java @@ -30,10 +30,12 @@ public class LoginScreen extends BaseScreen { private JPanel rootPanel; private JTextField licenseKeyField; private JLabel licenseLabel; + private JPasswordField confirmPasswordField; + private JLabel confirmPasswordLabel; @Override - protected String getScreenName() { - return "Authentication"; + public String getScreenName() { + return "Login"; } @Override @@ -41,45 +43,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 +177,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)); + rootPanel.setLayout(new GridLayoutManager(7, 3, new Insets(10, 10, 10, 10), -1, -1)); + final JPanel panel1 = new JPanel(); + 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(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(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(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(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"); - 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)); + 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"); - 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)); - 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)); - 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)); - 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)); - messageLabel = new JLabel(); - Font messageLabelFont = this.$$$getFont$$$(null, Font.PLAIN, -1, 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)); - 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)); - 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)); - 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(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(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"); - 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(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.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 17442f4..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,17 +44,14 @@ 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 - protected String getScreenName() { - return "Scan Details Screen"; + public String getScreenName() { + return "Scan Details"; } /** @@ -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)); } /** diff --git a/src/test/java/org/pwss/util/LoginUtilTest.java b/src/test/java/org/pwss/util/LoginUtilTest.java new file mode 100644 index 0000000..de79da5 --- /dev/null +++ b/src/test/java/org/pwss/util/LoginUtilTest.java @@ -0,0 +1,109 @@ +package org.pwss.util; + +import java.util.List; +import org.junit.jupiter.api.Test; +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 LoginUtilTest { + + // --- 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 testAllFieldsEmpty() { + LoginValidationResult result = LoginUtil.validateInput("", "", "", "", false); + assertFalse(result.isValid()); + 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 testNullInputsHandledGracefully() { + assertDoesNotThrow(() -> { + LoginValidationResult result = LoginUtil.validateInput(null, null, null, null, false); + assertFalse(result.isValid()); + assertTrue(result.errors().contains("Username cannot be empty.")); + }); + } + + // --- License key validation --- + + @Test + void testMissingLicenseKey() { + LoginValidationResult result = LoginUtil.validateInput("user", "password", "", "", false); + assertHasError(result, "License key cannot be empty."); + } + + // --- Password validation in createUserMode --- + + @Test + void testPasswordTooShort() { + LoginValidationResult result = LoginUtil.validateInput("user", "Ab1!", "Ab1!", "key123", true); + assertHasError(result, "Password must be at least 8 characters long."); + } + + @Test + void testConfirmPasswordMissing() { + LoginValidationResult result = LoginUtil.validateInput("user", "Abcd123!", "", "key123", true); + assertHasError(result, "Confirm Password cannot be empty."); + } + + @Test + void testPasswordsDoNotMatch() { + LoginValidationResult result = LoginUtil.validateInput("user", "Abcd123!", "Abcd1234!", "key123", true); + assertHasError(result, "Password and Confirm Password do not match."); + } + + @Test + void testPasswordMissingUppercase() { + LoginValidationResult result = LoginUtil.validateInput("user", "abcd123!", "abcd123!", "key123", true); + assertHasError(result, "Password must contain at least one uppercase letter."); + } + + @Test + void testPasswordMissingDigit() { + LoginValidationResult result = LoginUtil.validateInput("user", "Abcdefg!", "Abcdefg!", "key123", true); + assertHasError(result, "Password must contain at least one digit."); + } + + @Test + void testPasswordMissingSpecialCharacter() { + LoginValidationResult result = LoginUtil.validateInput("user", "Abcdefg1", "Abcdefg1", "key123", true); + assertHasError(result, "Password must contain at least one special character."); + } + + // --- Valid case --- + + @Test + void testValidInputInCreateUserMode() { + 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 = LoginUtil.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 = 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"); } } 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 }