diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java index 320a2200f7..52393fee8f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java @@ -49,6 +49,8 @@ final class AddImageWizardDataSourceSettingsVisual extends JPanel { private JPanel currentPanel; private final Map datasourceProcessorsMap = new HashMap<>(); + + private final PanelUpdateListener panelUpdateListener; private String currentDsp; @@ -60,6 +62,7 @@ final class AddImageWizardDataSourceSettingsVisual extends JPanel { AddImageWizardDataSourceSettingsVisual(AddImageWizardDataSourceSettingsPanel wizPanel) { initComponents(); this.wizPanel = wizPanel; + this.panelUpdateListener = new PanelUpdateListener(); typePanel.setLayout(new BorderLayout()); discoverDataSourceProcessors(); currentDsp = ImageDSProcessor.getType(); //default value to the ImageDSProcessor @@ -107,24 +110,37 @@ final class AddImageWizardDataSourceSettingsVisual extends JPanel { */ @SuppressWarnings("deprecation") private void updateCurrentPanel(JPanel panel) { + cleanupUpdateListener(currentPanel); currentPanel = panel; typePanel.removeAll(); typePanel.add(currentPanel, BorderLayout.CENTER); typePanel.validate(); typePanel.repaint(); - currentPanel.addPropertyChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString())) { - wizPanel.enableNextButton(getCurrentDSProcessor().isPanelValid()); - } - if (evt.getPropertyName().equals(DataSourceProcessor.DSP_PANEL_EVENT.FOCUS_NEXT.toString())) { - wizPanel.moveFocusToNext(); - } - } - }); + cleanupUpdateListener(currentPanel); + currentPanel.addPropertyChangeListener(panelUpdateListener); this.wizPanel.enableNextButton(getCurrentDSProcessor().isPanelValid()); } + + /** + * Removes PanelUpdateListener from panel if found. + * @param panel The panel from which to remove the listener. + */ + private void cleanupUpdateListener(JPanel panel) { + if (panel == null) { + return; + } + + PropertyChangeListener[] listeners = panel.getPropertyChangeListeners(); + if (listeners == null) { + return; + } + + for (PropertyChangeListener listener: listeners) { + if (listener instanceof PanelUpdateListener) { + panel.removePropertyChangeListener(listener); + } + } + } /** * Returns the currently selected DS Processor @@ -221,4 +237,20 @@ final class AddImageWizardDataSourceSettingsVisual extends JPanel { protected abstract boolean addSeparatorAfter(JList list, Object value, int index); } + + /** + * A property change listener that responds to update events. + */ + private class PanelUpdateListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString())) { + wizPanel.enableNextButton(getCurrentDSProcessor().isPanelValid()); + } + if (evt.getPropertyName().equals(DataSourceProcessor.DSP_PANEL_EVENT.FOCUS_NEXT.toString())) { + wizPanel.moveFocusToNext(); + } + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java index e7e22b5763..83bfb434f1 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java @@ -163,7 +163,9 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour */ @Override public boolean isPanelValid() { - return configPanel.validatePanel(); + // before attempting to validate the panel (a potentially long running operation), + // check if the validation is loading or on delay. + return !configPanel.isValidationLoading() && configPanel.validatePanel(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java index 585a81a1d2..166067478d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java @@ -67,7 +67,9 @@ public class ImageFilePanel extends JPanel { private static int VALIDATE_TIMEOUT_MILLIS = 1200; static ScheduledThreadPoolExecutor delayedValidationService = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("ImageFilePanel delayed validation").build()); - private Future validateAction = null; + private final Object validateActionLock = new Object(); + private Runnable validateAction = null; + private Future validateFuture = null; /** * Creates new form ImageFilePanel @@ -632,10 +634,6 @@ public class ImageFilePanel extends JPanel { showError(Bundle.ImageFilePanel_validatePanel_invalidSHA256()); return false; } - - if (!PathValidator.isValidForCaseType(path, Case.getCurrentCase().getCaseType())) { - showError(Bundle.ImageFilePanel_validatePanel_dataSourceOnCDriveError()); - } try { String password = this.getPassword(); @@ -652,8 +650,12 @@ public class ImageFilePanel extends JPanel { showError(Bundle.ImageFilePanel_validatePanel_unknownError()); return false; } - - showError(null); + + if (!PathValidator.isValidForCaseType(path, Case.getCurrentCase().getCaseType())) { + showError(Bundle.ImageFilePanel_validatePanel_dataSourceOnCDriveError()); + } else { + showError(null); + } return true; } @@ -683,6 +685,16 @@ public class ImageFilePanel extends JPanel { return true; } + + /** + * @return True if the panel is on a delay for validating (i.e. typing a + * password for bitlocker). + */ + public boolean isValidationLoading() { + synchronized (this.validateActionLock) { + return this.validateAction != null; + } + } public void storeSettings() { String imagePathName = getContentPaths(); @@ -746,23 +758,64 @@ public class ImageFilePanel extends JPanel { delayValidate(); } - private synchronized void delayValidate() { - if (ImageFilePanel.this.validateAction != null) { - ImageFilePanel.this.validateAction.cancel(true); + /** + * Run validation on a delay to avoid password checking too many times + * while typing. + */ + private void delayValidate() { + boolean triggerUpdate = false; + synchronized (ImageFilePanel.this.validateActionLock) { + if (ImageFilePanel.this.validateFuture != null && + !ImageFilePanel.this.validateFuture.isCancelled() && + !ImageFilePanel.this.validateFuture.isDone()) { + ImageFilePanel.this.validateFuture.cancel(true); + triggerUpdate = true; + } + + ImageFilePanel.this.validateAction = new ValidationRunnable(); + + ImageFilePanel.this.validateFuture = ImageFilePanel.this.delayedValidationService.schedule( + ImageFilePanel.this.validateAction, + VALIDATE_TIMEOUT_MILLIS, + TimeUnit.MILLISECONDS); } + // trigger invalidation after setting up new runnable if not already triggered + if (triggerUpdate) { + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } + errorLabel.setVisible(false); if (!ImageFilePanel.this.loadingLabel.isVisible()) { ImageFilePanel.this.loadingLabel.setVisible(true); } + } + + /** + * Runnable to run the updateHelper if the validation action remains + * this runnable. + */ + private class ValidationRunnable implements Runnable { - ImageFilePanel.this.validateAction = ImageFilePanel.this.delayedValidationService.schedule( - () -> { - ImageFilePanel.this.updateHelper(); - return null; - }, - VALIDATE_TIMEOUT_MILLIS, - TimeUnit.MILLISECONDS); + @Override + public void run() { + if (Thread.interrupted()) { + return; + } + + synchronized (ImageFilePanel.this.validateActionLock) { + if (ImageFilePanel.this.validateAction != this) { + return; + } + + // set the validation action to null to indicate that this is done running and can be validated. + ImageFilePanel.this.validateAction = null; + ImageFilePanel.this.validateFuture = null; + } + + ImageFilePanel.this.updateHelper(); + } + } } }